#------------------------------------------------------------------------
#
# 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 DvmPnm
package require DvmColor
package require DvmMpeg

source ../lib/pnmlib.tcl

#------------------------------------------------------------------------
# This is a simple example demonstrating how to encode a short sequence
# of PPM files into I and P frames with: 1 Sequence, 2 GOPs, 4 Picture
# frames per GOP, 1 Slice per frame, 1 quantization scale.  The P frames 
# are encoded using the original I frames.  The motion vector search is 
# done with full pel units.  The frame pattern is IPIPIP...
#
# Note: the input files should be named <prefix>000.ppm, <prefix>001.ppm ...
#------------------------------------------------------------------------

# check arguments
if {$argc != 2} {
    puts "enter the prefix of input file names: "
    set namePrefix  [gets stdin]
    puts "enter the output file name: "
    set outFileName [gets stdin]
} else {
    set namePrefix  [lindex $argv 0]
    set outFileName [lindex $argv 1]
}

# buffer large enough to hold 2 frames and some headers
set buffer_size         50000
set frames_per_second   30
set forward_f_code      3
# width and height of each frame in pixels
set width               176
set height              120

set pictures 0
set seconds 0
set minutes 0
set hours 0

#------------------------------------------------------------------------
# Use this to keep track of how many picture frames, seconds, minutes,
# hours.  This information is encoded in the GOP headers.
#------------------------------------------------------------------------
proc increment_time {} {
    global pictures seconds minutes hours
    incr pictures 1
    if { $pictures == 30 } {
        set pictures 0
        incr seconds 1
        if { $seconds == 60 } {
	    set seconds 0
	    incr minutes 1
            if { $minutes == 60 } {
                set minutes 0
                incr hours 1
            }
        }
    }
}


#------------------------------------------------------------------------
# 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
}

#------------------------------------------------------------------------
# Read in a PPM file into r, g, b ByteImages, then convert it to y, u, v
# with 4:2:0 sampling.
#------------------------------------------------------------------------
proc read_to_yuv {} {
uplevel #0 {
    set fileName [format "%s%03d.ppm" $namePrefix $pictures]
    set inFile [open $fileName r]
    puts [format "Processing %s" $fileName]
    fconfigure $inFile -translation binary -buffersize $buffer_size
    bitparser_wrap $bp $bs
    bitstream_channel_read $bs $inFile 0
    pnm_hdr_parse $bp $pnmHdr
    ppm_parse $bp $r $g $b
    close $inFile
    rgb_to_yuv_420 $r $g $b $y $u $v
}}

#------------------------------------------------------------------------
# initialization
#------------------------------------------------------------------------
set seqHdr [mpeg_seq_hdr_new]
set gopHdr [mpeg_gop_hdr_new]
set picHdr [mpeg_pic_hdr_new]
set pnmHdr [pnm_hdr_new]

set w $width
set h $height
set halfw [expr $w/2]
set halfh [expr $h/2]
set mbw [expr int(($w+15)/16)]
set mbh [expr int(($h+15)/16)]

# this should be enough for one frame
set sliceInfo {1000}

set r       [byte_new $w $h]
set g       [byte_new $w $h]
set b       [byte_new $w $h]
set y       [byte_new $w $h]
set prevY   [byte_new $w $h]
set u       [byte_new $halfw $halfh]
set v       [byte_new $halfw $halfh]
set prevU   [byte_new $halfw $halfh]
set prevV   [byte_new $halfw $halfh]
set qScale  [byte_new $mbw $mbh]
set scY     [sc_new [expr $mbw*2] [expr $mbh*2]]
set scU     [sc_new $mbw $mbh]
set scV     [sc_new $mbw $mbh]
set fmv     [vector_new $mbw $mbh]

set fileSize [file size ${namePrefix}000.ppm]
set bp  [bitparser_new]
set bs  [bitstream_new $fileSize]
set obp [bitparser_new]
set obs [bitstream_new $buffer_size]
bitparser_wrap $obp $obs
set outFile [open $outFileName w]
fconfigure $outFile -translation binary

byte_set $qScale 4

#------------------------------------------------------------------------
# encode
#------------------------------------------------------------------------
mpeg_seq_hdr_set_width          $seqHdr $w 
mpeg_seq_hdr_set_height         $seqHdr $h
mpeg_seq_hdr_set_aspect_ratio   $seqHdr 1.000
mpeg_seq_hdr_set_pic_rate       $seqHdr $frames_per_second
mpeg_seq_hdr_set_bit_rate       $seqHdr -1
mpeg_seq_hdr_set_buffer_size    $seqHdr 16
mpeg_seq_hdr_set_constrained    $seqHdr 0
mpeg_seq_hdr_set_default_iqt    $seqHdr
mpeg_seq_hdr_set_default_niqt   $seqHdr

mpeg_seq_hdr_encode $seqHdr $obp

# we are not going to change these
mpeg_gop_hdr_set_drop_frame_flag    $gopHdr 0 
mpeg_gop_hdr_set_closed_gop         $gopHdr 1
mpeg_gop_hdr_set_broken_link        $gopHdr 0

mpeg_pic_hdr_set_vbv_delay          $picHdr 0
mpeg_pic_hdr_set_full_pel_backward  $picHdr 0
mpeg_pic_hdr_set_backward_f_code    $picHdr 0

for {set gop 0} {$gop < 2} {incr gop 1} {
    mpeg_gop_hdr_set_hours      $gopHdr $hours
    mpeg_gop_hdr_set_minutes    $gopHdr $minutes
    mpeg_gop_hdr_set_seconds    $gopHdr $seconds
    mpeg_gop_hdr_set_pictures   $gopHdr $pictures

    mpeg_gop_hdr_encode $gopHdr $obp 
    set temporalRef 0
    for {set p 0} {$p < 2} {incr p 1} {

        # I FRAME #
        read_to_yuv

        mpeg_pic_hdr_set_temporal_ref       $picHdr $temporalRef
        mpeg_pic_hdr_set_type               $picHdr i-frame
        mpeg_pic_hdr_set_full_pel_forward   $picHdr 0
        mpeg_pic_hdr_encode $picHdr $obp
        mpeg_pic_hdr_set_forward_f_code     $picHdr 0

        byte_to_sc_i $y $qScale mpeg-intra $scY
        byte_to_sc_i $u $qScale mpeg-intra $scU
        byte_to_sc_i $v $qScale mpeg-intra $scV
        mpeg_pic_i_encode $picHdr $scY $scU $scV $qScale $sliceInfo $obp

        increment_time
        incr temporalRef 1
        swap y prevY
        swap u prevU
        swap v prevV
        
        # P FRAME #
        read_to_yuv

        mpeg_pic_hdr_set_temporal_ref       $picHdr $temporalRef
        mpeg_pic_hdr_set_type               $picHdr p-frame
        mpeg_pic_hdr_set_full_pel_forward   $picHdr 1
        mpeg_pic_hdr_set_forward_f_code     $picHdr $forward_f_code
        mpeg_pic_hdr_encode $picHdr $obp

        byte_p_motion_vec_search $picHdr $y $prevY {} $fmv
        byte_y_to_sc_p  $y $prevY $fmv $qScale mpeg-intra mpeg-non-intra $scY
        byte_uv_to_sc_p $u $prevU $fmv $qScale mpeg-intra mpeg-non-intra $scU 
        byte_uv_to_sc_p $v $prevV $fmv $qScale mpeg-intra mpeg-non-intra $scV
        mpeg_pic_p_encode $picHdr $scY $scU $scV $fmv $qScale $sliceInfo $obp

        increment_time
        incr temporalRef 1

        puts "Writing to file..."
        set size [bitparser_tell $obp]
        bitstream_channel_write_segment $obs $outFile 0 $size
        bitparser_wrap $obp $obs
    }
}
# don't forget this
mpeg_seq_end_code_encode $obp
set size [bitparser_tell $obp]
bitstream_channel_write_segment $obs $outFile 0 $size

close $outFile

#----------------------------------------------------------------
# clean up
#----------------------------------------------------------------
bitstream_free $bs
bitparser_free $bp
bitstream_free $obs
bitparser_free $obp

mpeg_pic_hdr_free $picHdr
mpeg_gop_hdr_free $gopHdr
mpeg_seq_hdr_free $seqHdr
pnm_hdr_free $pnmHdr

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 $qScale
sc_free $scY
sc_free $scU
sc_free $scV
vector_free $fmv