You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
391 lines
10 KiB
391 lines
10 KiB
// |
|
// AVI MJPEG encoder |
|
// |
|
|
|
include "riff.pixi" |
|
|
|
AVIH_FLAG_HASINDEX = 0x00000010 //Index at end of file; NOT RECOGNIZED BY MPLAYER & VLC :( |
|
AVIH_FLAG_MUSTUSEINDEX = 0x00000020 |
|
AVIH_FLAG_ISINTERLEAVED = 0x00000100 |
|
AVIH_FLAG_TRUSTCKTYPE = 0x00000800 //Use CKType to find key frames |
|
AVIH_FLAG_WASCAPTUREFILE = 0x00010000 |
|
AVIH_FLAG_COPYRIGHTED = 0x00020000 |
|
|
|
MJPEG_ENCODER_FLAG_HASSOUND = ( 1 << 0 ) |
|
MJPEG_ENCODER_FLAG_USEINDEX = ( 1 << 1 ) |
|
|
|
fn mjpeg_fn_error( $fn_name, $pars ) |
|
{ |
|
logf( "%s() MJPEG error: wrong number of parameters (%d)\n", $fn_name, $pars ) |
|
} |
|
|
|
fn mjpeg_encoder_open( |
|
$fps, |
|
$width, |
|
$height, |
|
$quality, |
|
$audio_channels, |
|
$audio_frames_per_second, |
|
$audio_sample_type, |
|
$flags, |
|
$output_stream ) |
|
{ |
|
if $0 != 9 { mjpeg_fn_error( "mjpeg_encoder_open", $0 ) ret( -1 ) } |
|
|
|
$mj = new( 1, 1, INT ) |
|
$mj.fps = $fps |
|
$mj.frames = 0 |
|
$mj.width = $width |
|
$mj.height = $height |
|
$mj.quality = $quality |
|
$mj.audio_ch = $audio_channels |
|
$mj.audio_freq = $audio_frames_per_second |
|
$mj.audio_type = $audio_sample_type |
|
$mj.flags = $flags |
|
$mj.out = $output_stream |
|
$mj.idx = new( 4, 1, INT32 ) |
|
$mj.idx_ptr = 0 |
|
$mj.jpeg = -1 |
|
$mj.silent_chunk = -1 |
|
|
|
$mj.audio_bits = 0 |
|
if $audio_sample_type == INT8 { $mj.audio_bits = 8 } |
|
if $audio_sample_type == INT16 { $mj.audio_bits = 16 } |
|
if $audio_sample_type == FLOAT32 { $mj.audio_bits = 32 } |
|
if $mj.audio_bits == 0 |
|
{ |
|
logf( "MJPEG encoder error: wrong audio sample type. Possible values: INT8, INT16, FLOAT32\n" ) |
|
ret( -1 ) |
|
} |
|
|
|
//AVI: |
|
|
|
$mj.c_avi = riff_chunk_open( -1, "RIFF", $mj.out ) |
|
riff_chunk_write_data( $mj.c_avi, "AVI ", 0, 0 ) |
|
|
|
//Header: |
|
|
|
$c_hdrl = riff_chunk_open( $mj.c_avi, "LIST", $mj.out ) |
|
riff_chunk_write_data( $c_hdrl, "hdrl", 0, 0 ) |
|
|
|
$c = riff_chunk_open( $c_hdrl, "avih", $mj.out ) |
|
$avih = new( 14, 1, INT32 ) |
|
clean( $avih ) |
|
$avih[ 0 ] = 1000000 div $mj.fps //microSecPerFrame |
|
$avih[ 1 ] = 10000000 //maxBytesPerSec |
|
$avih[ 3 ] = AVIH_FLAG_ISINTERLEAVED | AVIH_FLAG_TRUSTCKTYPE | AVIH_FLAG_WASCAPTUREFILE //flags |
|
if $flags & MJPEG_ENCODER_FLAG_USEINDEX |
|
{ |
|
$avih[ 3 ] | AVIH_FLAG_HASINDEX |
|
} |
|
$avih[ 4 ] = 0 //totalFrames |
|
$mj.fix1_offset = ftell( $mj.out ) + 4 * 4 |
|
if $flags & MJPEG_ENCODER_FLAG_HASSOUND { $avih[ 6 ] = 2 } else { $avih[ 6 ] = 1 } |
|
$avih[ 7 ] = 1024 * 1024 //suggestedBufferSize |
|
$avih[ 8 ] = $mj.width |
|
$avih[ 9 ] = $mj.height |
|
riff_chunk_write_data( $c, $avih, 0, 0 ) |
|
riff_chunk_close( $c ) |
|
remove( $avih ) |
|
|
|
//Video stream info: |
|
|
|
$c_strl = riff_chunk_open( $c_hdrl, "LIST", $mj.out ) |
|
riff_chunk_write_data( $c_strl, "strl", 0, 0 ) |
|
|
|
$c = riff_chunk_open( $c_strl, "strh", $mj.out ) |
|
$strh = new( 14, 1, INT32 ) |
|
clean( $strh ) |
|
$strh[ 0 ] = 'vids' //type |
|
$strh[ 1 ] = 'MJPG' //handler |
|
$strh[ 5 ] = 1 //scale |
|
$strh[ 6 ] = $fps //rate |
|
$strh[ 8 ] = 0 //length |
|
$mj.fix2_offset = ftell( $mj.out ) + 8 * 4 |
|
$strh[ 9 ] = 1024 * 1024 //suggestedBufferSize |
|
$strh[ 10 ] = -1 //quality |
|
$strh[ 13 ] = $width | ( $height << 16 ) |
|
riff_chunk_write_data( $c, $strh, 0, 0 ) |
|
riff_chunk_close( $c ) |
|
remove( $strh ) |
|
|
|
$c = riff_chunk_open( $c_strl, "strf", $mj.out ) |
|
$bmph = new( 10, 1, INT32 ) |
|
clean( $bmph ) |
|
$bmph[ 0 ] = get_size( $bmph ) * get_esize( $bmph ) //size |
|
$bmph[ 1 ] = $width |
|
$bmph[ 2 ] = $height |
|
$bmph[ 3 ] = 1 | ( 24 << 16 ) //planes and bitCount |
|
$bmph[ 4 ] = 'MJPG' //compression |
|
$bmph[ 5 ] = $width * $height * 3 //imgSize |
|
riff_chunk_write_data( $c, $bmph, 0, 0 ) |
|
riff_chunk_close( $c ) |
|
remove( $bmph ) |
|
|
|
riff_chunk_close( $c_strl ) |
|
|
|
if $flags & MJPEG_ENCODER_FLAG_HASSOUND |
|
{ |
|
//Audio stream info (uncompressed interleaved): |
|
|
|
$c_strl = riff_chunk_open( $c_hdrl, "LIST", $mj.out ) |
|
riff_chunk_write_data( $c_strl, "strl", 0, 0 ) |
|
|
|
$c = riff_chunk_open( $c_strl, "strh", $mj.out ) |
|
$strh = new( 14, 1, INT32 ) |
|
clean( $strh ) |
|
$strh[ 0 ] = 'auds' //type |
|
$strh[ 1 ] = 0 //handler |
|
$strh[ 5 ] = 1 //scale |
|
$strh[ 6 ] = $audio_frames_per_second //rate |
|
$strh[ 8 ] = 0 //length |
|
$mj.fix3_offset = ftell( $mj.out ) + 8 * 4 |
|
$strh[ 9 ] = 0 //suggestedBufferSize |
|
$strh[ 10 ] = -1 //quality |
|
$strh[ 11 ] = $audio_channels * ( $mj.audio_bits / 8 ) //sampleSize |
|
riff_chunk_write_data( $c, $strh, 0, 0 ) |
|
riff_chunk_close( $c ) |
|
remove( $strh ) |
|
|
|
$c = riff_chunk_open( $c_strl, "strf", $mj.out ) |
|
$fmt = new( 4, 1, INT32 ) |
|
clean( $fmt ) |
|
$audio_format = 1; if $mj.audio_bits == 32 { $audio_format = 3 } |
|
$fmt[ 0 ] = $audio_format | ( $audio_channels << 16 ) |
|
$fmt[ 1 ] = $audio_frames_per_second |
|
$fmt[ 2 ] = $audio_frames_per_second * $audio_channels * ( $mj.audio_bits / 8 ) //bytes per second |
|
$fmt[ 3 ] = $audio_channels * ( $mj.audio_bits / 8 ) | ( $mj.audio_bits << 16 ) //block align + bits |
|
riff_chunk_write_data( $c, $fmt, 0, 0 ) |
|
riff_chunk_close( $c ) |
|
remove( $fmt ) |
|
|
|
riff_chunk_close( $c_strl ) |
|
} |
|
|
|
riff_chunk_close( $c_hdrl ) |
|
|
|
//Video and Audio data: |
|
|
|
$mj.c_movi = riff_chunk_open( $mj.c_avi, "LIST", $mj.out ) |
|
riff_chunk_write_data( $mj.c_movi, "movi", 0, 0 ) |
|
|
|
ret( $mj ) |
|
} |
|
|
|
/*fn mjpeg_encoder_jfif_to_avi1( $jpeg ) |
|
{ |
|
if $0 != 1 { mjpeg_fn_error( "mjpeg_encoder_jfif_to_avi1", $0 ) ret( 0 ) } |
|
|
|
$p = -1 |
|
$i = 0 while $i < 256 |
|
{ |
|
if $jpeg[ $i ] == 'J' |
|
{ |
|
if $jpeg[ $i + 1 ] == 'F' |
|
{ |
|
if $jpeg[ $i + 2 ] == 'I' |
|
{ |
|
if $jpeg[ $i + 3 ] == 'F' |
|
{ |
|
$p = $i |
|
break |
|
} |
|
} |
|
} |
|
} |
|
$i + 1 |
|
} |
|
if $p >= 0 |
|
{ |
|
$size = get_size( $jpeg ) |
|
$jpeg[ $p ] = 'A' $p + 1 |
|
$jpeg[ $p ] = 'V' $p + 1 |
|
$jpeg[ $p ] = 'I' $p + 1 |
|
$jpeg[ $p ] = '1' $p + 1 |
|
$jpeg[ $p ] = 0 $p + 1 |
|
$jpeg[ $p ] = 0 $p + 1 |
|
$jpeg[ $p ] = $size & 255 $p + 1 |
|
$jpeg[ $p ] = ( $size >> 8 ) & 255 $p + 1 |
|
$jpeg[ $p ] = ( $size >> 16 ) & 255 $p + 1 |
|
$jpeg[ $p ] = ( $size >> 24 ) & 255 $p + 1 |
|
$jpeg[ $p ] = $size & 255 $p + 1 |
|
$jpeg[ $p ] = ( $size >> 8 ) & 255 $p + 1 |
|
$jpeg[ $p ] = ( $size >> 16 ) & 255 $p + 1 |
|
$jpeg[ $p ] = ( $size >> 24 ) & 255 $p + 1 |
|
} |
|
|
|
ret( 0 ) |
|
}*/ |
|
|
|
fn mjpeg_encoder_write_jpeg( $mj, $jpeg, $offset_bytes, $size_bytes ) |
|
{ |
|
if $0 != 4 { mjpeg_fn_error( "mjpeg_encoder_write_jpeg", $0 ) ret( 0 ) } |
|
if $mj <= 0 { ret( 0 ) } |
|
|
|
if $mj.flags & MJPEG_ENCODER_FLAG_USEINDEX |
|
{ |
|
mjpeg_encoder_add_index( $mj, '00dc', 0x10, ftell( $mj.out ) - ( $mj.c_movi.size_ptr + 4 ), $size_bytes ) |
|
} |
|
|
|
$c = riff_chunk_open( $mj.c_movi, "00dc", $mj.out ) |
|
riff_chunk_write_data( $c, $jpeg, $offset_bytes, $size_bytes ) |
|
riff_chunk_close( $c ) |
|
|
|
ret( $size_bytes ) |
|
} |
|
|
|
fn mjpeg_encoder_write_image( $mj, $img ) |
|
{ |
|
if $0 != 2 { mjpeg_fn_error( "mjpeg_encoder_write_image", $0 ) ret( 0 ) } |
|
if $mj <= 0 { ret( 0 ) } |
|
|
|
if $mj.jpeg <= 0 |
|
{ |
|
$mj.jpeg = new( 1, 1, INT8 ) |
|
} |
|
$jpeg = $mj.jpeg |
|
if $img != -999 |
|
{ |
|
$f = fopen_mem( $jpeg ) |
|
fsave( $img, $f, FORMAT_JPEG, $mj.quality ) |
|
fclose( $f ) |
|
} |
|
|
|
if $mj.flags & MJPEG_ENCODER_FLAG_USEINDEX |
|
{ |
|
mjpeg_encoder_add_index( $mj, '00dc', 0x10, ftell( $mj.out ) - ( $mj.c_movi.size_ptr + 4 ), get_size( $jpeg ) ) |
|
} |
|
|
|
$c = riff_chunk_open( $mj.c_movi, "00dc", $mj.out ) |
|
riff_chunk_write_data( $c, $jpeg, 0, 0 ) |
|
riff_chunk_close( $c ) |
|
|
|
ret( get_size( $jpeg ) ) |
|
} |
|
|
|
fn mjpeg_encoder_get_audio_size( $mj ) //Number of audio frames per chunk |
|
{ |
|
if $0 != 1 { mjpeg_fn_error( "mjpeg_encoder_get_audio_size", $0 ) ret( 0 ) } |
|
if $mj <= 0 { ret( 0 ) } |
|
|
|
$len1 = ( ( $mj.frames - 1 ) * $mj.audio_freq ) div $mj.fps |
|
$len2 = ( $mj.frames * $mj.audio_freq ) div $mj.fps |
|
$len = $len2 - $len1 |
|
|
|
ret( $len ) |
|
} |
|
|
|
fn mjpeg_encoder_write_audio( $mj, $audio, $offset_bytes, $size_bytes ) |
|
{ |
|
if $0 != 4 { mjpeg_fn_error( "mjpeg_encoder_write_audio", $0 ) ret( 0 ) } |
|
if $mj <= 0 { ret( 0 ) } |
|
|
|
$bytes = mjpeg_encoder_get_audio_size( $mj ) * $mj.audio_ch * ( $mj.audio_bits div 8 ) |
|
|
|
if $mj.c_01wb <= 0 |
|
{ |
|
if $mj.flags & MJPEG_ENCODER_FLAG_USEINDEX |
|
{ |
|
mjpeg_encoder_add_index( $mj, '01wb', 0, ftell( $mj.out ) - ( $mj.c_movi.size_ptr + 4 ), $bytes ) |
|
} |
|
$mj.c_01wb = riff_chunk_open( $mj.c_movi, "01wb", $mj.out ) |
|
} |
|
|
|
if $audio <= 0 |
|
{ |
|
//Silent chunk: |
|
if $mj.silent_chunk <= 0 |
|
{ |
|
$mj.silent_chunk = new( $bytes, 1, INT8 ) |
|
clean( $mj.silent_chunk ) |
|
} |
|
$audio = $mj.silent_chunk |
|
} |
|
|
|
riff_chunk_write_data( $mj.c_01wb, $audio, $offset_bytes, $size_bytes ) |
|
|
|
ret( $size_bytes ) |
|
} |
|
|
|
fn mjpeg_encoder_next_frame( $mj ) |
|
{ |
|
if $0 != 1 { mjpeg_fn_error( "mjpeg_encoder_next_frame", $0 ) ret( -1 ) } |
|
if $mj <= 0 { ret( -1 ) } |
|
|
|
if $mj.c_01wb > 0 |
|
{ |
|
//Close audio chunk: |
|
riff_chunk_close( $mj.c_01wb ) |
|
$mj.c_01wb = -1 |
|
} |
|
|
|
$mj.frames + 1 |
|
|
|
ret( 0 ) |
|
} |
|
|
|
fn mjpeg_encoder_add_index( $mj, $id, $flags, $offset, $size ) |
|
{ |
|
if $0 != 5 { mjpeg_fn_error( "mjpeg_encoder_add_index", $0 ) ret( -1 ) } |
|
if $mj <= 0 { ret( -1 ) } |
|
|
|
$idx = $mj.idx |
|
$idx_ptr = $mj.idx_ptr |
|
$idx[ $idx_ptr * 4 + 0 ] = $id |
|
$idx[ $idx_ptr * 4 + 1 ] = $flags |
|
$idx[ $idx_ptr * 4 + 2 ] = $offset |
|
$idx[ $idx_ptr * 4 + 3 ] = $size |
|
$idx_ptr + 1 |
|
if $idx_ptr >= get_size( $idx ) / 4 |
|
{ |
|
resize( $idx, ( $idx_ptr + 256 ) * 4, 1, INT32 ) |
|
} |
|
$mj.idx_ptr = $idx_ptr |
|
|
|
ret( 0 ) |
|
} |
|
|
|
fn mjpeg_encoder_close( $mj ) |
|
{ |
|
if $0 != 1 { mjpeg_fn_error( "mjpeg_encoder_close", $0 ) ret( -1 ) } |
|
if $mj <= 0 { ret( -1 ) } |
|
|
|
if $mj.frames <= 0 |
|
{ |
|
logf( "MJPEG encoder error: no frames\n" ) |
|
} |
|
|
|
riff_chunk_close( $mj.c_movi ) |
|
|
|
//Index: |
|
|
|
if $mj.flags & MJPEG_ENCODER_FLAG_USEINDEX |
|
{ |
|
$idx = $mj.idx |
|
$idx_ptr = $mj.idx_ptr |
|
$c = riff_chunk_open( $mj.c_avi, "idx1", $mj.out ) |
|
riff_chunk_write_data( $c, $idx, 0, $idx_ptr * 4 * 4 ) |
|
riff_chunk_close( $c ) |
|
} |
|
|
|
//Fixup: |
|
|
|
fseek( $mj.out, $mj.fix1_offset, SEEK_SET ) |
|
riff_write_int32( $mj.frames, $mj.out ) //totalFrames |
|
fseek( $mj.out, $mj.fix2_offset, SEEK_SET ) |
|
riff_write_int32( $mj.frames, $mj.out ) //video stream length |
|
if $mj.flags & MJPEG_ENCODER_FLAG_HASSOUND |
|
{ |
|
fseek( $mj.out, $mj.fix3_offset, SEEK_SET ) |
|
riff_write_int32( ( $mj.frames * $mj.audio_freq ) div $mj.fps, $mj.out ) //audio stream length (number of frames) |
|
} |
|
|
|
riff_chunk_close( $mj.c_avi ) |
|
|
|
remove( $mj.silent_chunk ) |
|
remove( $mj.jpeg ) |
|
remove( $mj.idx ) |
|
remove( $mj ) |
|
|
|
ret( 0 ) |
|
}
|
|
|