mirror of
https://github.com/etlegacy/etlegacy-libs.git
synced 2025-02-24 12:11:11 +00:00
926 lines
26 KiB
C
926 lines
26 KiB
C
/********************************************************************
|
|
* *
|
|
* THIS FILE IS PART OF THE OggTheora SOFTWARE CODEC SOURCE CODE. *
|
|
* USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS *
|
|
* GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE *
|
|
* IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. *
|
|
* *
|
|
* THE Theora SOURCE CODE IS COPYRIGHT (C) 2002-2004 *
|
|
* by the Xiph.Org Foundation http://www.xiph.org/ *
|
|
* *
|
|
********************************************************************
|
|
|
|
function: example encoder application; makes an Ogg Theora/Vorbis
|
|
file from YUV4MPEG2 and WAV input
|
|
last mod: $Id: transcoder_example.c,v 1.4 2004/03/20 00:14:04 tterribe Exp $
|
|
|
|
********************************************************************/
|
|
|
|
#define _GNU_SOURCE
|
|
#define _REENTRANT
|
|
#define _LARGEFILE_SOURCE
|
|
#define _LARGEFILE64_SOURCE
|
|
#define _FILE_OFFSET_BITS 64
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <getopt.h>
|
|
#include <time.h>
|
|
#include <math.h>
|
|
#include "theora/theora.h"
|
|
#include "vorbis/codec.h"
|
|
#include "vorbis/vorbisenc.h"
|
|
|
|
#ifdef _WIN32
|
|
/*supply missing headers and functions to Win32. going to hell, I know*/
|
|
#include <io.h>
|
|
#include <fcntl.h>
|
|
|
|
static double rint(double x)
|
|
{
|
|
if (x < 0.0)
|
|
return (double)(int)(x - 0.5);
|
|
else
|
|
return (double)(int)(x + 0.5);
|
|
}
|
|
#endif
|
|
|
|
/*Copied from vorbis/sharedbook.c*/
|
|
static int _ilog(unsigned int v){
|
|
int ret=0;
|
|
while(v){
|
|
ret++;
|
|
v>>=1;
|
|
}
|
|
return(ret);
|
|
}
|
|
|
|
|
|
const char *optstring = "o:a:A:v:V:";
|
|
struct option options [] = {
|
|
{"output",required_argument,NULL,'o'},
|
|
{"audio-rate-target",required_argument,NULL,'A'},
|
|
{"video-rate-target",required_argument,NULL,'V'},
|
|
{"audio-quality",required_argument,NULL,'a'},
|
|
{"video-quality",required_argument,NULL,'v'},
|
|
{NULL,0,NULL,0}
|
|
};
|
|
|
|
typedef struct TC_INSTANCE {
|
|
ogg_uint32_t LastKeyFrame ;
|
|
ogg_int64_t KeyFrameCount;
|
|
int ThisIsFirstFrame;
|
|
int ThisIsKeyFrame;
|
|
ogg_uint32_t CurrentFrame;
|
|
ogg_int64_t granulepos;
|
|
int keyframe_granule_shift;
|
|
char * in_bytes;
|
|
long in_bytecount;
|
|
ogg_uint32_t fps_denominator;
|
|
ogg_uint32_t fps_numerator;
|
|
oggpack_buffer opb_in;
|
|
oggpack_buffer opb_out;
|
|
} TC_INSTANCE;
|
|
|
|
/* You'll go to Hell for using globals. */
|
|
|
|
FILE *audio=NULL;
|
|
FILE *video=NULL;
|
|
|
|
int audio_ch=0;
|
|
int audio_hz=0;
|
|
|
|
float audio_q=.1;
|
|
int audio_r=-1;
|
|
|
|
int video_x=0;
|
|
int video_y=0;
|
|
int frame_x=0;
|
|
int frame_y=0;
|
|
int frame_x_offset=0;
|
|
int frame_y_offset=0;
|
|
int video_hzn=0;
|
|
int video_hzd=0;
|
|
int video_an=0;
|
|
int video_ad=0;
|
|
|
|
int video_r=-1;
|
|
int video_q=16;
|
|
|
|
char *vp3frame[2];
|
|
int framebytecount[2];
|
|
int frameiskey[2];
|
|
|
|
ogg_page audiopage;
|
|
ogg_page videopage;
|
|
|
|
static void usage(void){
|
|
fprintf(stderr,
|
|
"Usage: encoder_example [options] [audio_file] video_file\n\n"
|
|
"Options: \n\n"
|
|
" -o --output <filename.ogv> file name for encoded output;\n"
|
|
" If this option is not given, the\n"
|
|
" compressed data is sent to stdout.\n\n"
|
|
" -A --audio-rate-target <n> bitrate target for Vorbis audio;\n"
|
|
" use -a and not -A if at all possible,\n"
|
|
" as -a gives higher quality for a given\n"
|
|
" bitrate.\n\n"
|
|
" -V --video-rate-target <n> bitrate target for Theora video\n\n"
|
|
" -a --audio-quality <n> Vorbis quality selector from -1 to 10\n"
|
|
" (-1 yields smallest files but lowest\n"
|
|
" fidelity; 10 yields highest fidelity\n"
|
|
" but large files. '2' is a reasonable\n"
|
|
" default).\n\n"
|
|
" -v --video-quality <n> Theora quality selector fro 0 to 10\n"
|
|
" (0 yields smallest files but lowest\n"
|
|
" video quality. 10 yields highest\n"
|
|
" fidelity but large files).\n\n"
|
|
"encoder_example accepts only uncompressed RIFF WAV format audio and\n"
|
|
"YUV4MPEG2 uncompressed video.\n\n");
|
|
exit(1);
|
|
}
|
|
|
|
static void id_file(char *f){
|
|
FILE *test;
|
|
unsigned char buffer[80];
|
|
int ret;
|
|
|
|
/* open it, look for magic */
|
|
|
|
if(!strcmp(f,"-")){
|
|
/* stdin */
|
|
test=stdin;
|
|
}else{
|
|
test=fopen(f,"rb");
|
|
if(!test){
|
|
fprintf(stderr,"Unable to open file %s.\n",f);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
ret=fread(buffer,1,4,test);
|
|
if(ret<4){
|
|
fprintf(stderr,"EOF determining file type of file %s.\n",f);
|
|
exit(1);
|
|
}
|
|
|
|
if(!memcmp(buffer,"RIFF",4)){
|
|
/* possible WAV file */
|
|
|
|
if(audio){
|
|
/* umm, we already have one */
|
|
fprintf(stderr,"Multiple RIFF WAVE files specified on command line.\n");
|
|
exit(1);
|
|
}
|
|
|
|
/* Parse the rest of the header */
|
|
|
|
ret=fread(buffer,1,4,test);
|
|
ret=fread(buffer,1,4,test);
|
|
if(ret<4)goto riff_err;
|
|
if(!memcmp(buffer,"WAVE",4)){
|
|
|
|
while(!feof(test)){
|
|
ret=fread(buffer,1,4,test);
|
|
if(ret<4)goto riff_err;
|
|
if(!memcmp("fmt",buffer,3)){
|
|
|
|
/* OK, this is our audio specs chunk. Slurp it up. */
|
|
|
|
ret=fread(buffer,1,20,test);
|
|
if(ret<20)goto riff_err;
|
|
|
|
if(memcmp(buffer+4,"\001\000",2)){
|
|
fprintf(stderr,"The WAV file %s is in a compressed format; "
|
|
"can't read it.\n",f);
|
|
exit(1);
|
|
}
|
|
|
|
audio=test;
|
|
audio_ch=buffer[6]+(buffer[7]<<8);
|
|
audio_hz=buffer[8]+(buffer[9]<<8)+
|
|
(buffer[10]<<16)+(buffer[11]<<24);
|
|
|
|
if(buffer[18]+(buffer[19]<<8)!=16){
|
|
fprintf(stderr,"Can only read 16 bit WAV files for now.\n");
|
|
exit(1);
|
|
}
|
|
|
|
/* Now, align things to the beginning of the data */
|
|
/* Look for 'dataxxxx' */
|
|
while(!feof(test)){
|
|
ret=fread(buffer,1,4,test);
|
|
if(ret<4)goto riff_err;
|
|
if(!memcmp("data",buffer,4)){
|
|
/* We're there. Ignore the declared size for now. */
|
|
ret=fread(buffer,1,4,test);
|
|
if(ret<4)goto riff_err;
|
|
|
|
fprintf(stderr,"File %s is 16 bit %d channel %d Hz RIFF WAV audio.\n",
|
|
f,audio_ch,audio_hz);
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fprintf(stderr,"Couldn't find WAVE data in RIFF file %s.\n",f);
|
|
exit(1);
|
|
|
|
}
|
|
if(!memcmp(buffer,"AVI2",4)){
|
|
/* possible AVI2VP31 format file */
|
|
/* read until newline, or 80 cols, whichever happens first */
|
|
int i;
|
|
for(i=0;i<79;i++){
|
|
ret=fread(buffer+i,1,1,test);
|
|
if(ret<1)goto yuv_err;
|
|
if(buffer[i]=='\n')break;
|
|
}
|
|
if(i==79){
|
|
fprintf(stderr,"Error parsing %s header; not a VP31 raw frames file?\n",f);
|
|
}
|
|
buffer[i]='\0';
|
|
|
|
if(!memcmp(buffer,"VP31",4)){
|
|
char interlace;
|
|
|
|
if(video){
|
|
/* umm, we already have one */
|
|
fprintf(stderr,"Multiple video files specified on command line.\n");
|
|
exit(1);
|
|
}
|
|
|
|
if(buffer[4]!='R'){
|
|
fprintf(stderr,"Incorrect file ; VP31 raw frames required.\n");
|
|
}
|
|
|
|
ret=sscanf(buffer,"VP31R W%d H%d F%d:%d I%c A%d:%d",
|
|
&frame_x,&frame_y,&video_hzn,&video_hzd,&interlace,
|
|
&video_an,&video_ad);
|
|
if(ret<7){
|
|
fprintf(stderr,"Error parsing AVI2VP31R header in file %s.\n",f);
|
|
exit(1);
|
|
}
|
|
|
|
if(interlace!='p'){
|
|
fprintf(stderr,"Input video is interlaced; Theora handles only progressive scan\n");
|
|
exit(1);
|
|
}
|
|
|
|
video=test;
|
|
|
|
fprintf(stderr,"File %s is %dx%d %.02f fps VP31 video.\n",
|
|
f,frame_x,frame_y,(double)video_hzn/video_hzd);
|
|
|
|
return;
|
|
}
|
|
}
|
|
fprintf(stderr,"Input file %s is neither a WAV nor VP31 file.\n",f);
|
|
exit(1);
|
|
|
|
riff_err:
|
|
fprintf(stderr,"EOF parsing RIFF file %s.\n",f);
|
|
exit(1);
|
|
yuv_err:
|
|
fprintf(stderr,"EOF parsing VP31 file %s.\n",f);
|
|
exit(1);
|
|
|
|
}
|
|
|
|
int spinner=0;
|
|
char *spinascii="|/-\\";
|
|
void spinnit(void){
|
|
spinner++;
|
|
if(spinner==4)spinner=0;
|
|
fprintf(stderr,"\r%c",spinascii[spinner]);
|
|
}
|
|
|
|
int fetch_and_process_audio(FILE *audio,ogg_page *audiopage,
|
|
ogg_stream_state *vo,
|
|
vorbis_dsp_state *vd,
|
|
vorbis_block *vb,
|
|
int audioflag){
|
|
ogg_packet op;
|
|
int i,j;
|
|
|
|
while(audio && !audioflag){
|
|
/* process any audio already buffered */
|
|
spinnit();
|
|
if(ogg_stream_pageout(vo,audiopage)>0) return 1;
|
|
if(ogg_stream_eos(vo))return 0;
|
|
|
|
{
|
|
/* read and process more audio */
|
|
signed char readbuffer[4096];
|
|
int toread=4096/2/audio_ch;
|
|
int bytesread=fread(readbuffer,1,toread*2*audio_ch,audio);
|
|
int sampread=bytesread/2/audio_ch;
|
|
float **vorbis_buffer;
|
|
int count=0;
|
|
|
|
if(bytesread<=0){
|
|
/* end of file. this can be done implicitly, but it's
|
|
easier to see here in non-clever fashion. Tell the
|
|
library we're at end of stream so that it can handle the
|
|
last frame and mark end of stream in the output properly */
|
|
vorbis_analysis_wrote(vd,0);
|
|
}else{
|
|
vorbis_buffer=vorbis_analysis_buffer(vd,sampread);
|
|
/* uninterleave samples */
|
|
for(i=0;i<sampread;i++){
|
|
for(j=0;j<audio_ch;j++){
|
|
vorbis_buffer[j][i]=((readbuffer[count+1]<<8)|
|
|
(0x00ff&(int)readbuffer[count]))/32768.f;
|
|
count+=2;
|
|
}
|
|
}
|
|
|
|
vorbis_analysis_wrote(vd,sampread);
|
|
|
|
}
|
|
|
|
while(vorbis_analysis_blockout(vd,vb)==1){
|
|
|
|
/* analysis, assume we want to use bitrate management */
|
|
vorbis_analysis(vb,NULL);
|
|
vorbis_bitrate_addblock(vb);
|
|
|
|
/* weld packets into the bitstream */
|
|
while(vorbis_bitrate_flushpacket(vd,&op))
|
|
ogg_stream_packetin(vo,&op);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return audioflag;
|
|
}
|
|
|
|
int theora_transcode_packetout( TC_INSTANCE *ttc, int last_p, ogg_packet *op){
|
|
|
|
long bytes=ttc->in_bytecount;
|
|
|
|
if(!bytes)return(0);
|
|
|
|
op->packet=ttc->in_bytes;
|
|
op->bytes=bytes;
|
|
op->b_o_s=0;
|
|
op->e_o_s=last_p;
|
|
|
|
op->packetno=ttc->CurrentFrame;
|
|
op->granulepos=ttc->granulepos;
|
|
|
|
return 1;
|
|
}
|
|
|
|
void TranscodeKeyFrame(TC_INSTANCE *ttc){
|
|
/* Keep track of the total number of Key Frames Coded */
|
|
ttc->KeyFrameCount += 1;
|
|
ttc->LastKeyFrame = 1;
|
|
}
|
|
|
|
void TranscodeFrame(TC_INSTANCE *ttc){
|
|
ttc->LastKeyFrame++;
|
|
}
|
|
|
|
void TranscodeFirstFrame(TC_INSTANCE *ttc){
|
|
/* Keep track of the total number of Key Frames Coded. */
|
|
ttc->KeyFrameCount = 1;
|
|
ttc->LastKeyFrame = 1;
|
|
}
|
|
|
|
int theora_transcode_bufferin( TC_INSTANCE *ttc, int isKeyFrame, char * bytes, int bytecount){
|
|
|
|
/*transcode: record keyframe flag*/
|
|
ttc->ThisIsKeyFrame = isKeyFrame;
|
|
|
|
/* Special case for first frame */
|
|
if ( ttc->ThisIsFirstFrame ){
|
|
ttc->ThisIsFirstFrame = 0;
|
|
ttc->ThisIsKeyFrame = 0;
|
|
} else if ( ttc->ThisIsKeyFrame ) {
|
|
TranscodeKeyFrame(ttc);
|
|
ttc->ThisIsKeyFrame = 0;
|
|
} else {
|
|
/* Compress the frame. */
|
|
TranscodeFrame( ttc );
|
|
}
|
|
/*need to pack info here*/
|
|
{
|
|
|
|
int frame_type;
|
|
long total_bits;
|
|
long total_words;
|
|
int frac_bits;
|
|
|
|
oggpackB_readinit(&ttc->opb_in,bytes,bytecount);
|
|
oggpackB_reset(&ttc->opb_out);
|
|
|
|
/*Mark as video frame.*/
|
|
oggpackB_write(&ttc->opb_out,0,1);
|
|
/*Copy frame type.*/
|
|
frame_type=oggpackB_read1(&ttc->opb_in);
|
|
oggpackB_write(&ttc->opb_out,frame_type,1);
|
|
/*Skip an unused bit in the VP32 header.*/
|
|
oggpackB_adv1(&ttc->opb_in);
|
|
/*Copy Q multiplier.*/
|
|
oggpackB_write(&ttc->opb_out,oggpackB_read(&ttc->opb_in,6),6);
|
|
/*VP3 has no per-block Q multipliers*/
|
|
oggpackB_write(&ttc->opb_out,0,1);
|
|
/*If the frame is a base/key/golden frame, copy a few extra bits.*/
|
|
if(frame_type==0){
|
|
/*These 13 bits are not included in a Theora frame header.
|
|
They were 0's and VP3 version info in VP32.*/
|
|
oggpackB_adv(&ttc->opb_in,13);
|
|
/*Copy the key frame type and the spare configuration bits.*/
|
|
oggpackB_write(&ttc->opb_out,oggpackB_read(&ttc->opb_in,3),3);
|
|
}
|
|
|
|
/*Copy the rest of the bits over.*/
|
|
total_bits=bytecount*8-oggpack_bits(&ttc->opb_in);
|
|
frac_bits=(int)(total_bits&31);
|
|
if(frac_bits){
|
|
oggpackB_write(&ttc->opb_out,oggpackB_read(&ttc->opb_in,frac_bits),
|
|
frac_bits);
|
|
}
|
|
total_words=total_bits>>5;
|
|
while(total_words-->0){
|
|
oggpackB_write(&ttc->opb_out,oggpackB_read(&ttc->opb_in,32),32);
|
|
}
|
|
|
|
ttc->in_bytecount = oggpackB_bytes(&ttc->opb_out);
|
|
ttc->in_bytes = oggpackB_get_buffer(&ttc->opb_out);
|
|
}
|
|
|
|
/* Update stats variables. */
|
|
ttc->CurrentFrame++;
|
|
|
|
ttc->granulepos=
|
|
((ttc->CurrentFrame-ttc->LastKeyFrame-1)<<ttc->keyframe_granule_shift)+
|
|
ttc->LastKeyFrame-1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
//static void _tp_writebuffer(oggpack_buffer *opb, const char *buf, const long len)
|
|
|
|
int theora_transcoder_init(theora_info * ti, TC_INSTANCE * ttc){
|
|
memset(ttc, 0, sizeof(*ttc));
|
|
ttc->granulepos = -1;
|
|
ttc->keyframe_granule_shift=_ilog(ti->keyframe_frequency_force-1);
|
|
ttc->LastKeyFrame = 0;
|
|
ttc->KeyFrameCount = 0;
|
|
ttc->ThisIsFirstFrame = 1;
|
|
ttc->ThisIsKeyFrame = 0;
|
|
ttc->CurrentFrame = 1;
|
|
ttc->in_bytes = 0;
|
|
ttc->in_bytecount = 0;
|
|
ttc->fps_denominator = ti->fps_denominator;
|
|
ttc->fps_numerator = ti->fps_numerator;
|
|
oggpackB_writeinit(&ttc->opb_out);
|
|
return 0;
|
|
}
|
|
|
|
int fetch_and_process_video(FILE *video,ogg_page *videopage,
|
|
ogg_stream_state *to,
|
|
TC_INSTANCE *ttc,
|
|
int videoflag){
|
|
/* You'll go to Hell for using static variables */
|
|
static int state=-1;
|
|
ogg_packet op;
|
|
int i;
|
|
int keyframeflag, framelength;
|
|
|
|
|
|
if(state==-1){
|
|
/* initialize the double frame buffer */
|
|
state=0;
|
|
}
|
|
|
|
/* is there a video page flushed? If not, work until there is. */
|
|
while(!videoflag){
|
|
spinnit();
|
|
|
|
if(ogg_stream_pageout(to,videopage)>0) return 1;
|
|
if(ogg_stream_eos(to)) return 0;
|
|
|
|
{
|
|
/* read and process more video */
|
|
/* video strategy reads one frame ahead so we know when we're
|
|
at end of stream and can mark last video frame as such
|
|
(vorbis audio has to flush one frame past last video frame
|
|
due to overlap and thus doesn't need this extra work */
|
|
|
|
/* have two frame buffers full (if possible) before
|
|
proceeding. after first pass and until eos, one will
|
|
always be full when we get here */
|
|
|
|
for(i=state;i<2;i++){
|
|
char c,frame[6];
|
|
int ret=fread(frame,1,6,video);
|
|
|
|
/* match and skip the frame header */
|
|
if(ret<6)break;
|
|
if(memcmp(frame,"FRAME",5)){
|
|
fprintf(stderr,"Loss of framing in VP31 input data\n");
|
|
exit(1);
|
|
}
|
|
if(frame[5]!='\n'){
|
|
int j;
|
|
for(j=0;j<79;j++)
|
|
if(fread(&c,1,1,video)&&c=='\n')break;
|
|
if(j==79){
|
|
fprintf(stderr,"Error parsing VP31 frame header\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/*read the length*/
|
|
ret=fread(&framelength, sizeof(int), 1, video);
|
|
|
|
/*read the keyframeflag*/
|
|
ret=fread(&keyframeflag, sizeof(int), 1, video);
|
|
|
|
vp3frame[i] = malloc(framelength);
|
|
framebytecount[i] = framelength;
|
|
frameiskey[i] = keyframeflag;
|
|
|
|
/* read the frame */
|
|
ret=fread((char *) vp3frame[i], sizeof(char), framelength, video);
|
|
if(ret!=framelength) break;
|
|
|
|
state++;
|
|
}
|
|
|
|
if(state<1){
|
|
/* can't get here unless VP31 stream has no video */
|
|
fprintf(stderr,"Video input contains no frames.\n");
|
|
exit(1);
|
|
}
|
|
|
|
/* Theora is a one-frame-in,one-frame-out system; submit a frame
|
|
for compression and pull out the packet */
|
|
|
|
//theora_encode_YUVin(td,&yuv);
|
|
theora_transcode_bufferin( ttc, frameiskey[0], vp3frame[0], framebytecount[0]);
|
|
|
|
/* if there's only one frame, it's the last in the stream */
|
|
if(state<2)
|
|
theora_transcode_packetout(ttc,1,&op);
|
|
else
|
|
theora_transcode_packetout(ttc,0,&op);
|
|
|
|
ogg_stream_packetin(to,&op);
|
|
|
|
{
|
|
signed char *temp=vp3frame[0];
|
|
vp3frame[0]=vp3frame[1];
|
|
vp3frame[1] = temp;
|
|
free(temp);
|
|
|
|
framebytecount[0]= framebytecount[1];
|
|
frameiskey[0] = frameiskey[1];
|
|
state--;
|
|
}
|
|
}
|
|
}
|
|
return videoflag;
|
|
}
|
|
|
|
/* returns, in seconds, absolute time of current packet in given
|
|
logical stream */
|
|
double transcode_granule_time(TC_INSTANCE *ttc,ogg_int64_t granulepos){
|
|
if(granulepos>=0){
|
|
ogg_int64_t iframe=granulepos>>ttc->keyframe_granule_shift;
|
|
ogg_int64_t pframe=granulepos-(iframe<<ttc->keyframe_granule_shift);
|
|
|
|
return (iframe+pframe)*
|
|
((double)ttc->fps_denominator/ttc->fps_numerator);
|
|
|
|
}
|
|
return(-1);
|
|
}
|
|
|
|
int main(int argc,char *argv[]){
|
|
int c,long_option_index,ret;
|
|
|
|
ogg_stream_state to; /* take physical pages, weld into a logical
|
|
stream of packets */
|
|
ogg_stream_state vo; /* take physical pages, weld into a logical
|
|
stream of packets */
|
|
ogg_page og; /* one Ogg bitstream page. Vorbis packets are inside */
|
|
ogg_packet op; /* one raw packet of data for decode */
|
|
|
|
theora_state td;
|
|
theora_info ti;
|
|
theora_comment tc;
|
|
|
|
vorbis_info vi; /* struct that stores all the static vorbis bitstream
|
|
settings */
|
|
vorbis_comment vc; /* struct that stores all the user comments */
|
|
|
|
vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */
|
|
vorbis_block vb; /* local working space for packet->PCM decode */
|
|
|
|
int audioflag=0;
|
|
int videoflag=0;
|
|
int akbps=0;
|
|
int vkbps=0;
|
|
|
|
ogg_int64_t audio_bytesout=0;
|
|
ogg_int64_t video_bytesout=0;
|
|
double timebase;
|
|
|
|
FILE* outfile = stdout;
|
|
|
|
TC_INSTANCE ttc;
|
|
|
|
#ifdef _WIN32 /* We need to set stdin/stdout to binary mode. Damn windows. */
|
|
/* if we were reading/writing a file, it would also need to in
|
|
binary mode, eg, fopen("file.wav","wb"); */
|
|
/* Beware the evil ifdef. We avoid these where we can, but this one we
|
|
cannot. Don't add any more, you'll probably go to hell if you do. */
|
|
_setmode( _fileno( stdin ), _O_BINARY );
|
|
_setmode( _fileno( stdout ), _O_BINARY );
|
|
#endif
|
|
|
|
while((c=getopt_long(argc,argv,optstring,options,&long_option_index))!=EOF){
|
|
switch(c){
|
|
case 'o':
|
|
outfile=fopen(optarg,"wb");
|
|
if(outfile==NULL){
|
|
fprintf(stderr,"Unable to open output file '%s'\n", optarg);
|
|
exit(1);
|
|
}
|
|
break;;
|
|
|
|
case 'a':
|
|
audio_q=atof(optarg)*.099;
|
|
if(audio_q<-.1 || audio_q>1){
|
|
fprintf(stderr,"Illegal audio quality (choose -1 through 10)\n");
|
|
exit(1);
|
|
}
|
|
audio_r=-1;
|
|
break;
|
|
|
|
case 'v':
|
|
video_q=rint(atof(optarg)*6.3);
|
|
if(video_q<0 || video_q>63){
|
|
fprintf(stderr,"Illegal video quality (choose 0 through 10)\n");
|
|
exit(1);
|
|
}
|
|
video_r=0;
|
|
break;
|
|
|
|
case 'A':
|
|
audio_r=atof(optarg)*1000;
|
|
if(audio_q<0){
|
|
fprintf(stderr,"Illegal audio quality (choose > 0 please)\n");
|
|
exit(1);
|
|
}
|
|
audio_q=-99;
|
|
break;
|
|
|
|
case 'V':
|
|
video_r=rint(atof(optarg)*1000);
|
|
if(video_r<45000 || video_r>2000000){
|
|
fprintf(stderr,"Illegal video bitrate (choose 45kbps through 2000kbps)\n");
|
|
exit(1);
|
|
}
|
|
video_q=0;
|
|
break;
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
|
|
while(optind<argc){
|
|
/* assume that anything following the options must be a filename */
|
|
id_file(argv[optind]);
|
|
optind++;
|
|
}
|
|
|
|
/* yayness. Set up Ogg output stream */
|
|
srand(time(NULL));
|
|
ogg_stream_init(&vo,rand());
|
|
ogg_stream_init(&to,rand()); /* oops, add one ot the above */
|
|
|
|
/* Set up Theora encoder */
|
|
if(!video){
|
|
fprintf(stderr,"No video files submitted for compression?\n");
|
|
exit(1);
|
|
}
|
|
/* Theora has a divisible-by-sixteen restriction for the encoded video size */
|
|
/* scale the frame size up to the nearest /16 and calculate offsets */
|
|
video_x=((frame_x + 15) >>4)<<4;
|
|
video_y=((frame_y + 15) >>4)<<4;
|
|
frame_x_offset=(video_x-frame_x)/2;
|
|
frame_y_offset=(video_y-frame_y)/2;
|
|
|
|
theora_info_init(&ti);
|
|
ti.width=video_x;
|
|
ti.height=video_y;
|
|
ti.frame_width=frame_x;
|
|
ti.frame_height=frame_y;
|
|
ti.offset_x=frame_x_offset;
|
|
ti.offset_y=frame_y_offset;
|
|
ti.fps_numerator=video_hzn;
|
|
ti.fps_denominator=video_hzd;
|
|
ti.aspect_numerator=video_an;
|
|
ti.aspect_denominator=video_ad;
|
|
ti.colorspace=OC_CS_UNSPECIFIED;
|
|
ti.target_bitrate=video_r;
|
|
ti.quality=video_q;
|
|
|
|
ti.dropframes_p=0;
|
|
ti.quick_p=1;
|
|
ti.keyframe_auto_p=1;
|
|
ti.keyframe_frequency=32768;
|
|
ti.keyframe_frequency_force=32768;
|
|
ti.keyframe_data_target_bitrate=video_r*1.5;
|
|
ti.keyframe_auto_threshold=80;
|
|
ti.keyframe_mindistance=8;
|
|
ti.noise_sensitivity=1;
|
|
|
|
theora_encode_init(&td,&ti);
|
|
theora_transcoder_init(&ti, &ttc);
|
|
theora_info_clear(&ti);
|
|
|
|
/* initialize Vorbis too, assuming we have audio to compress. */
|
|
if(audio){
|
|
vorbis_info_init(&vi);
|
|
if(audio_q>-99)
|
|
ret = vorbis_encode_init_vbr(&vi,audio_ch,audio_hz,audio_q);
|
|
else
|
|
ret = vorbis_encode_init(&vi,audio_ch,audio_hz,-1,audio_r,-1);
|
|
if(ret){
|
|
fprintf(stderr,"The Vorbis encoder could not set up a mode according to\n"
|
|
"the requested quality or bitrate.\n\n");
|
|
exit(1);
|
|
}
|
|
|
|
vorbis_comment_init(&vc);
|
|
vorbis_analysis_init(&vd,&vi);
|
|
vorbis_block_init(&vd,&vb);
|
|
}
|
|
|
|
/* write the bitstream header packets with proper page interleave */
|
|
|
|
/* first packet will get its own page automatically */
|
|
theora_encode_header(&td,&op);
|
|
ogg_stream_packetin(&to,&op);
|
|
if(ogg_stream_pageout(&to,&og)!=1){
|
|
fprintf(stderr,"Internal Ogg library error.\n");
|
|
exit(1);
|
|
}
|
|
fwrite(og.header,1,og.header_len,outfile);
|
|
fwrite(og.body,1,og.body_len,outfile);
|
|
|
|
/* create the remaining theora headers */
|
|
theora_comment_init(&tc);
|
|
theora_encode_comment(&tc,&op);
|
|
ogg_stream_packetin(&to,&op);
|
|
theora_encode_tables(&td,&op);
|
|
ogg_stream_packetin(&to,&op);
|
|
|
|
if(audio){
|
|
ogg_packet header;
|
|
ogg_packet header_comm;
|
|
ogg_packet header_code;
|
|
|
|
vorbis_analysis_headerout(&vd,&vc,&header,&header_comm,&header_code);
|
|
ogg_stream_packetin(&vo,&header); /* automatically placed in its own
|
|
page */
|
|
if(ogg_stream_pageout(&vo,&og)!=1){
|
|
fprintf(stderr,"Internal Ogg library error.\n");
|
|
exit(1);
|
|
}
|
|
fwrite(og.header,1,og.header_len,outfile);
|
|
fwrite(og.body,1,og.body_len,outfile);
|
|
|
|
/* remaining vorbis header packets */
|
|
ogg_stream_packetin(&vo,&header_comm);
|
|
ogg_stream_packetin(&vo,&header_code);
|
|
}
|
|
|
|
/* Flush the rest of our headers. This ensures
|
|
the actual data in each stream will start
|
|
on a new page, as per spec. */
|
|
while(1){
|
|
int result = ogg_stream_flush(&to,&og);
|
|
if(result<0){
|
|
/* can't get here */
|
|
fprintf(stderr,"Internal Ogg library error.\n");
|
|
exit(1);
|
|
}
|
|
if(result==0)break;
|
|
fwrite(og.header,1,og.header_len,outfile);
|
|
fwrite(og.body,1,og.body_len,outfile);
|
|
}
|
|
if(audio){
|
|
while(1){
|
|
int result=ogg_stream_flush(&vo,&og);
|
|
if(result<0){
|
|
/* can't get here */
|
|
fprintf(stderr,"Internal Ogg library error.\n");
|
|
exit(1);
|
|
}
|
|
if(result==0)break;
|
|
fwrite(og.header,1,og.header_len,outfile);
|
|
fwrite(og.body,1,og.body_len,outfile);
|
|
}
|
|
}
|
|
|
|
/* setup complete. Raw processing loop */
|
|
fprintf(stderr,"Compressing....\n");
|
|
while(1){
|
|
|
|
|
|
/* is there an audio page flushed? If not, fetch one if possible */
|
|
audioflag=fetch_and_process_audio(audio,&audiopage,&vo,&vd,&vb,audioflag);
|
|
|
|
/* is there a video page flushed? If not, fetch one if possible */
|
|
videoflag=fetch_and_process_video(video,&videopage,&to,&ttc,videoflag);
|
|
|
|
/* no pages of either? Must be end of stream. */
|
|
if(!audioflag && !videoflag)break;
|
|
|
|
/* which is earlier; the end of the audio page or the end of the
|
|
video page? Flush the earlier to stream */
|
|
{
|
|
int audio_or_video=-1;
|
|
double audiotime=
|
|
audioflag?vorbis_granule_time(&vd,ogg_page_granulepos(&audiopage)):-1;
|
|
double videotime=
|
|
videoflag?transcode_granule_time(&ttc,ogg_page_granulepos(&videopage)):-1;
|
|
|
|
if(!audioflag){
|
|
audio_or_video=1;
|
|
} else if(!videoflag) {
|
|
audio_or_video=0;
|
|
} else {
|
|
if(audiotime<videotime)
|
|
audio_or_video=0;
|
|
else
|
|
audio_or_video=1;
|
|
}
|
|
|
|
if(audio_or_video==1){
|
|
/* flush a video page */
|
|
video_bytesout+=fwrite(videopage.header,1,videopage.header_len,outfile);
|
|
video_bytesout+=fwrite(videopage.body,1,videopage.body_len,outfile);
|
|
videoflag=0;
|
|
timebase=videotime;
|
|
|
|
}else{
|
|
/* flush an audio page */
|
|
audio_bytesout+=fwrite(audiopage.header,1,audiopage.header_len,outfile);
|
|
audio_bytesout+=fwrite(audiopage.body,1,audiopage.body_len,outfile);
|
|
audioflag=0;
|
|
timebase=audiotime;
|
|
}
|
|
{
|
|
int hundredths=timebase*100-(long)timebase*100;
|
|
int seconds=(long)timebase%60;
|
|
int minutes=((long)timebase/60)%60;
|
|
int hours=(long)timebase/3600;
|
|
|
|
if(audio_or_video)
|
|
vkbps=rint(video_bytesout*8./timebase*.001);
|
|
else
|
|
akbps=rint(audio_bytesout*8./timebase*.001);
|
|
|
|
fprintf(stderr,
|
|
"\n %d:%02d:%02d.%02d audio: %dkbps video: %dkbps ",
|
|
hours,minutes,seconds,hundredths,akbps,vkbps);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* clear out state */
|
|
|
|
if(audio){
|
|
ogg_stream_clear(&vo);
|
|
vorbis_block_clear(&vb);
|
|
vorbis_dsp_clear(&vd);
|
|
vorbis_comment_clear(&vc);
|
|
vorbis_info_clear(&vi);
|
|
}
|
|
if(video){
|
|
ogg_stream_clear(&to);
|
|
theora_clear(&td);
|
|
}
|
|
|
|
if(outfile && outfile!=stdout)fclose(outfile);
|
|
|
|
fprintf(stderr,"\r \ndone.\n\n");
|
|
|
|
return(0);
|
|
|
|
}
|