#------------------------------------------------------------------------
#
# Copyright (c) 1997-1998 by Cornell University.
# 
# See the file "license.txt" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
#------------------------------------------------------------------------
package require DvmBasic
package require DvmMpeg
package require DvmPnm
package require DvmColor

#---------------------------------------------------------------
# This script converts a MPEG Video Sequence to a series of ppm
# file.
#---------------------------------------------------------------

if {$argc != 1} {
    puts "enter input mpeg file :"
    set inname [gets stdin]
} else {
    set inname [lindex $argv 0]
}

#---------------------------------------------------------------
# This procedure encode 3 byte image into a bitstream bs,
# using bitparser bp, and output it to a tcl channel called
# name.  Assumes that the header is already encoded in the 
# bitstream.  (This is an improvement over the routines in pnmlib.tcl
# since it reuse the same header and bitstream)
#---------------------------------------------------------------
proc write_ppm {r g b bs bp name} {
    set chan [open $name w]
    fconfigure $chan -translation binary -buffersize 65536
    set curr [bitparser_tell $bp]
    ppm_encode $r $g $b $bp
    bitstream_channel_write $bs $chan 0
    bitparser_seek $bp $curr
    close $chan
}

#---------------------------------------------------------------
# swap the value of two pointers.
#---------------------------------------------------------------
proc swap {a b} {
    upvar $a aa
    upvar $b bb
    set temp $aa
    set aa $bb
    set bb $temp
}

#---------------------------------------------------------------
# This proc make sure that there are at least size bytes of 
# data in the bitstream bs, which is attached to bitparser bp.  
# If there is not enough data, fill up the bitstream by reading 
# from tcl channel chan.
#---------------------------------------------------------------
proc check_bitstream_underflow {bs bp chan size} {
    set off  [bitparser_tell $bp]
    set left [bitstream_bytes_left $bs $off]
    if {$left < $size} {
        bitstream_shift $bs $off
        bitstream_channel_read $bs $chan $left
        bitparser_seek $bp 0
    }
}
	
#----------------------------------------------------------------
# open file, create new bitparser, new bitstream, read first 
# 65535 bytes from file into bitstream and attached the bitparser
# to the bitstream
#----------------------------------------------------------------
set bp   [bitparser_new]
set bs   [bitstream_new 65535]
set file [open $inname r]
fconfigure $file -translation binary -buffersize 65535
bitstream_channel_read $bs $file 0
bitparser_wrap $bp $bs

#----------------------------------------------------------------
# parse the mpeg sequence header and find out the dimension and
# size of the frames.  Care must be taken if frame dimenion is 
# not multiple of 16
#----------------------------------------------------------------
set sh [mpeg_seq_hdr_new]
mpeg_seq_hdr_find $bp
mpeg_seq_hdr_parse $bp $sh
set seqw   [mpeg_seq_hdr_get_width $sh]
set seqh   [mpeg_seq_hdr_get_height $sh]
set picSize [mpeg_seq_hdr_get_buffer_size $sh]
set remw [expr $seqw % 16]
set remh [expr $seqh % 16]
if {$remw != 0} { 
    set w [expr $seqw + 16 - $remw]
} else {
    set w $seqw
}
if {$remh != 0} { 
    set h [expr $seqh + 16 - $remh]
} else {
    set h $seqh
}
set halfw [expr $w/2]
set halfh [expr $h/2]

#----------------------------------------------------------------
# allocate a bunch of byte buffer, sc buffer, mv buffer,
# mpeg_pic_hdr
#----------------------------------------------------------------
set y       [byte_new $w $h]
set prevy   [byte_new $w $h]
set futurey [byte_new $w $h]
set r       [byte_new $seqw $seqh]
set g       [byte_new $seqw $seqh]
set b       [byte_new $seqw $seqh]
set u       [byte_new $halfw $halfh]
set prevu   [byte_new $halfw $halfh]
set futureu [byte_new $halfw $halfh]
set v       [byte_new $halfw $halfh]
set prevv   [byte_new $halfw $halfh]
set futurev [byte_new $halfw $halfh]
set outy    [byte_clip $y 0 0 $seqw $seqh]
set outu    [byte_clip $u 0 0 [expr $seqw/2] [expr $seqh/2]]
set outv    [byte_clip $v 0 0 [expr $seqw/2] [expr $seqh/2]]
set fwdmv   [vector_new [expr $w/16] [expr $h/16]]
set bwdmv   [vector_new [expr $w/16] [expr $h/16]]
set scy     [sc_new [expr $w/8] [expr $h/8]]
set scu     [sc_new [expr $w/16] [expr $h/16]]
set scv     [sc_new [expr $w/16] [expr $h/16]]
set fh      [mpeg_pic_hdr_new]

#----------------------------------------------------------------
# initialize stuff for ppm file output
# we only need to write the header once.
#----------------------------------------------------------------
set pnmhdr  [pnm_hdr_new]
pnm_hdr_set_type   $pnmhdr "ppm-bin"
pnm_hdr_set_width  $pnmhdr $seqw 
pnm_hdr_set_height $pnmhdr $seqh
pnm_hdr_set_maxval $pnmhdr 255
set outbs   [bitstream_new [expr 3*$seqw*$seqh + 20]]
set outbp   [bitparser_new]
bitparser_wrap $outbp $outbs
pnm_hdr_encode $pnmhdr $outbp
pnm_hdr_free $pnmhdr

#----------------------------------------------------------------
# Now start decoding the video frames.  In each loop we make sure 
# there are at least picSize bytes available in the bitstream buffer.
#----------------------------------------------------------------
mpeg_pic_hdr_find $bp
set count 0
set gopSize 0
set gopStart 0

while {1} {
    check_bitstream_underflow $bs $bp $file $picSize
    mpeg_pic_hdr_parse $bp $fh
    set type [mpeg_pic_hdr_get_type $fh]
    set temporalRef [mpeg_pic_hdr_get_temporal_ref $fh]
    set count [expr $gopStart + $temporalRef]
    incr gopSize 1
    
    if {$type == "i"} {

        swap futurey prevy
        swap futureu prevu
        swap futurev prevv

        mpeg_pic_i_parse $bp $sh $fh $scy $scu $scv
        sc_i_to_byte $scy $y
        sc_i_to_byte $scu $u
        sc_i_to_byte $scv $v
        yuv_to_rgb_420 $y $u $v $r $g $b
        write_ppm $r $g $b $outbs $outbp [format "%03di.ppm" $count] 

        swap y futurey
        swap u futureu
        swap v futurev

    } elseif { $type == "p"} {

        swap futurey prevy
        swap futureu prevu
        swap futurev prevv

        mpeg_pic_p_parse $bp $sh $fh $scy $scu $scv $fwdmv
        sc_p_to_y  $scy $fwdmv $prevy $y
        sc_p_to_uv $scu $fwdmv $prevu $u
        sc_p_to_uv $scv $fwdmv $prevv $v
        yuv_to_rgb_420 $y $u $v $r $g $b
        write_ppm $r $g $b $outbs $outbp [format "%03dp.ppm" $count]

        swap y futurey
        swap u futureu
        swap v futurev
        
    } else {

        mpeg_pic_b_parse $bp $sh $fh $scy $scu $scv $fwdmv $bwdmv 
        sc_b_to_y  $scy $fwdmv $bwdmv $prevy $futurey $y
        sc_b_to_uv $scu $fwdmv $bwdmv $prevu $futureu $u
        sc_b_to_uv $scv $fwdmv $bwdmv $prevv $futurev $v
        yuv_to_rgb_420 $y $u $v $r $g $b
        write_ppm $r $g $b $outbs $outbp [format "%03db.ppm" $count] 

    }
    set currCode [mpeg_get_curr_start_code $bp]
    if {$currCode == "gop-start-code"} {
        incr gopStart $gopSize
        set gopSize 0
    }
    mpeg_pic_hdr_find $bp
    if {$currCode == "seq-end-code"} {
        break
    }
}
#----------------------------------------------------------------
# clean up
#----------------------------------------------------------------
mpeg_pic_hdr_free $fh
mpeg_seq_hdr_free $sh
bitstream_free $bs
bitparser_free $bp
byte_free $r
byte_free $g
byte_free $b
byte_free $y
byte_free $u
byte_free $v
byte_free $prevy
byte_free $prevu
byte_free $prevv
byte_free $futurey
byte_free $futureu
byte_free $futurev
sc_free $scy
sc_free $scu
sc_free $scv
vector_free $fwdmv
vector_free $bwdmv