static void *ucil_theora_encode_thread( ucil_theora_video_file_object_t *vobj ) { yuv_buffer yuv; double videopos = 0; double audiopos = 0; int gotpage = 0; unsigned char *ds_y_buffer = NULL; unsigned char *ds_u_buffer = NULL; unsigned char *ds_v_buffer = NULL; yuv.y_width = vobj->ti.width; yuv.y_height = vobj->ti.height; yuv.y_stride = vobj->ti.width; yuv.uv_width = vobj->ti.width / 2; yuv.uv_height = vobj->ti.height / 2; yuv.uv_stride = vobj->ti.width / 2; if( vobj->downsize > 1 || vobj->requires_resizing_frames ) { ds_y_buffer = malloc( yuv.y_width * yuv.y_height ); ds_u_buffer = malloc( yuv.uv_width * yuv.uv_height ); ds_v_buffer = malloc( yuv.uv_width * yuv.uv_height ); } vobj->last_frame = NULL; while( !vobj->quit_thread ) { unicap_data_buffer_t *data_buffer; ogg_page og; sem_wait( &vobj->lock ); data_buffer = ( unicap_data_buffer_t *)g_queue_pop_head( vobj->full_queue ); sem_post( &vobj->lock ); if( !data_buffer ) { audiopos = fetch_and_process_audio( vobj, audiopos ); usleep( 1000 ); continue; } if( vobj->frame_count == 0 ) { memcpy( &vobj->recording_start_time, &data_buffer->fill_time, sizeof( struct timeval ) ); } audiopos = fetch_and_process_audio( vobj, audiopos ); /* printf( "v: %f a: %f\n", videopos, audiopos ); */ if( vobj->audio && ( videopos > audiopos ) ) { data_buffer->flags &= ~UNICAP_FLAGS_BUFFER_LOCKED; sem_wait( &vobj->lock ); g_queue_push_head( vobj->empty_queue, data_buffer ); sem_post( &vobj->lock ); continue; } if( vobj->fill_frames ) { if( vobj->audio ) { if( vobj->last_frame ) { unicap_data_buffer_t *last_data_buffer; double streampos; struct timeval streamtime; last_data_buffer = vobj->last_frame; if( vobj->downsize > 1 || vobj->requires_resizing_frames ) { yuv.y = ds_y_buffer; yuv.u = ds_u_buffer; yuv.v = ds_v_buffer; } else { yuv.y = last_data_buffer->data; yuv.u = last_data_buffer->data + ( yuv.y_stride * yuv.y_height ); yuv.v = yuv.u + ( yuv.uv_stride * yuv.uv_height ); } streamtime.tv_sec = data_buffer->fill_time.tv_sec - vobj->recording_start_time.tv_sec; streamtime.tv_usec = data_buffer->fill_time.tv_usec; if( data_buffer->fill_time.tv_usec < vobj->recording_start_time.tv_usec ) { streamtime.tv_sec--; streamtime.tv_usec += 1000000; } streamtime.tv_usec -= vobj->recording_start_time.tv_usec; streampos = streamtime.tv_sec + ( (double)streamtime.tv_usec / 1000000.0f ); // If the streampos is ahead, this means that we get // less than 30 frames per seconds. // --> Fill up the stream while( streampos > videopos ) { /* printf( "s v: %f a: %f\n", videopos, audiopos ); */ gotpage = 0; if( theora_encode_YUVin( &vobj->th, &yuv ) ) { TRACE( "theora_encode_YUVin FAILED!\n" ); } theora_encode_packetout( &vobj->th, 0, &vobj->op ); ogg_stream_packetin( &vobj->os, &vobj->op ); while( ogg_stream_pageout( &vobj->os, &og ) ) { double gt; fwrite( og.header, og.header_len, 1, vobj->f ); fwrite( og.body, og.body_len, 1, vobj->f ); gt = theora_granule_time( &vobj->th, ogg_page_granulepos( &og ) ); if( gt < 0 ) { continue; } gotpage = 1; videopos = gt; /* printf( "THEORA: %f\n", videopos ); */ } if( !gotpage ) { videopos += vobj->frame_interval / 1000000.0f; } vobj->frame_count++; audiopos = fetch_and_process_audio( vobj, audiopos ); } // while( ( videopos + ( vobj->frame_interval / 1000000.0f ) ) < audiopos ) { /* printf( "a v: %f a: %f\n", videopos, audiopos ); */ gotpage = 0; if( theora_encode_YUVin( &vobj->th, &yuv ) ) { TRACE( "theora_encode_YUVin FAILED!\n" ); } theora_encode_packetout( &vobj->th, 0, &vobj->op ); ogg_stream_packetin( &vobj->os, &vobj->op ); while( ogg_stream_pageout( &vobj->os, &og ) ) { double gt; fwrite( og.header, og.header_len, 1, vobj->f ); fwrite( og.body, og.body_len, 1, vobj->f ); gt = theora_granule_time( &vobj->th, ogg_page_granulepos( &og ) ); if( gt < 0 ) { continue; } gotpage = 1; videopos = gt; /* printf( "THEORA: %f\n", videopos ); */ } if( !gotpage ) { videopos += vobj->frame_interval / 1000000.0f; } vobj->frame_count++; audiopos = fetch_and_process_audio( vobj, audiopos ); } last_data_buffer->flags &= ~UNICAP_FLAGS_BUFFER_LOCKED; sem_wait( &vobj->lock ); g_queue_push_head( vobj->empty_queue, vobj->last_frame ); sem_post( &vobj->lock ); vobj->last_frame = NULL; } } else { fill_frames( vobj, data_buffer, &yuv, ds_y_buffer, ds_u_buffer, ds_v_buffer ); } } else// ( !vobj->fill_frames ) { if( vobj->last_frame ) { unicap_data_buffer_t *last_data_buffer; last_data_buffer = vobj->last_frame; last_data_buffer->flags &= ~UNICAP_FLAGS_BUFFER_LOCKED; sem_wait( &vobj->lock ); g_queue_push_head( vobj->empty_queue, vobj->last_frame ); sem_post( &vobj->lock ); } vobj->last_frame = NULL; } // // Encode the new buffer // if( vobj->encode_frame_cb ) { vobj->encode_frame_cb( UNICAP_EVENT_NEW_FRAME, NULL, data_buffer, vobj->encode_frame_cb_data ); } vobj->frame_count++; if( vobj->downsize > 1 || vobj->requires_resizing_frames ) { downsize_yuv420p( vobj->format.size.width, vobj->format.size.height, vobj->downsize, vobj->ti.width, vobj->ti.height, ds_y_buffer, ds_u_buffer, ds_v_buffer, data_buffer->data, data_buffer->data + ( vobj->format.size.width * vobj->format.size.height ), data_buffer->data + ( vobj->format.size.width * vobj->format.size.height ) + ( ( vobj->format.size.width * vobj->format.size.height ) / 4 ) ); yuv.y = ds_y_buffer; yuv.u = ds_u_buffer; yuv.v = ds_v_buffer; } else { yuv.y = data_buffer->data; yuv.u = data_buffer->data + ( yuv.y_stride * yuv.y_height ); yuv.v = yuv.u + ( yuv.uv_stride * yuv.uv_height ); } if( theora_encode_YUVin( &vobj->th, &yuv ) ) { TRACE( "theora_encode_YUVin FAILED!\n" ); } memcpy( &vobj->last_frame_time, &data_buffer->fill_time, sizeof( struct timeval ) ); vobj->last_frame = data_buffer; theora_encode_packetout( &vobj->th, 0, &vobj->op ); ogg_stream_packetin( &vobj->os, &vobj->op ); /* printf( "= v: %f a: %f\n", videopos, audiopos ); */ gotpage = 0; while( ogg_stream_pageout( &vobj->os, &og ) ) { double gt; fwrite( og.header, og.header_len, 1, vobj->f ); fwrite( og.body, og.body_len, 1, vobj->f ); gt = theora_granule_time( &vobj->th, ogg_page_granulepos( &og ) ); if( gt < 0 ) { continue; } gotpage = 1; videopos = gt; /* printf( "THEORA: %f\n", videopos ); */ } if( !gotpage ) { videopos += vobj->frame_interval / 1000000.0f; } } if( vobj->last_frame ) { // encode again to set eos unicap_data_buffer_t *last_data_buffer; ogg_page og; ogg_packet op; #if HAVE_ALSA if( vobj->audio && !vobj->async_audio_encoding ) { audiopos = fetch_and_process_audio( vobj, audiopos ); vorbis_analysis_wrote( &vobj->vd, 0 ); while( vorbis_analysis_blockout( &vobj->vd, &vobj->vb ) == 1 ) { vorbis_analysis( &vobj->vb, NULL ); vorbis_bitrate_addblock( &vobj->vb ); while( vorbis_bitrate_flushpacket( &vobj->vd, &op ) ) { ogg_stream_packetin( &vobj->vo, &op ); } } while( ogg_stream_pageout( &vobj->vo, &og ) ) { fwrite( og.header, og.header_len, 1, vobj->f ); fwrite( og.body, og.body_len, 1, vobj->f ); } } else if( vobj->audio ) { audiopos = fetch_and_process_audio( vobj, audiopos ); } #endif last_data_buffer = vobj->last_frame; if( vobj->downsize > 1 || vobj->requires_resizing_frames ) { yuv.y = ds_y_buffer; yuv.u = ds_u_buffer; yuv.v = ds_v_buffer; } else { yuv.y = last_data_buffer->data; yuv.u = last_data_buffer->data + ( yuv.y_stride * yuv.y_height ); yuv.v = yuv.u + ( yuv.uv_stride * yuv.uv_height ); } if( theora_encode_YUVin( &vobj->th, &yuv ) ) { TRACE( "theora_encode_YUVin FAILED!\n" ); } theora_encode_packetout( &vobj->th, 1, &vobj->op ); ogg_stream_packetin( &vobj->os, &vobj->op ); while( ogg_stream_pageout( &vobj->os, &og ) ) { /* printf( "THEORA: %f\n", theora_granule_time( &vobj->th, ogg_page_granulepos( &og ) ) ); */ fwrite( og.header, og.header_len, 1, vobj->f ); fwrite( og.body, og.body_len, 1, vobj->f ); } last_data_buffer->flags &= ~UNICAP_FLAGS_BUFFER_LOCKED; sem_wait( &vobj->lock ); g_queue_push_head( vobj->empty_queue, vobj->last_frame ); sem_post( &vobj->lock ); vobj->last_frame = NULL; } return NULL; }
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; #ifdef _WIN32 # ifdef THEORA_PERF_DATA LARGE_INTEGER start_time; LARGE_INTEGER final_time; LONGLONG elapsed_ticks; LARGE_INTEGER ticks_per_second; LONGLONG elapsed_secs; LONGLONG elapsed_sec_mod; double elapsed_secs_dbl ; # endif /* 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; case 's': video_an=rint(atof(optarg)); break; case 'S': video_ad=rint(atof(optarg)); break; case 'f': video_hzn=rint(atof(optarg)); break; case 'F': video_hzd=rint(atof(optarg)); break; default: usage(); } } while(optind<argc){ /* assume that anything following the options must be a filename */ id_file(argv[optind]); optind++; } #ifdef THEORA_PERF_DATA # ifdef WIN32 QueryPerformanceCounter(&start_time); # endif #endif /* yayness. Set up Ogg output stream */ srand(time(NULL)); { /* need two inequal serial numbers */ int serial1, serial2; serial1 = rand(); serial2 = rand(); if (serial1 == serial2) serial2++; ogg_stream_init(&to,serial1); ogg_stream_init(&vo,serial2); } /* 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; /* We force the offset to be even. This ensures that the chroma samples align properly with the luma samples. */ frame_x_offset=((video_x-frame_x)/2)&~1; frame_y_offset=((video_y-frame_y)/2)&~1; 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.pixelformat=OC_PF_420; 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=64; ti.keyframe_frequency_force=64; 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_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_comment() doesn't take a theora_state parameter, so it has to allocate its own buffer to pass back the packet data. If we don't free it here, we'll leak. libogg2 makes this much cleaner: the stream owns the buffer after you call packetin in libogg2, but this is not true in libogg1.*/ free(op.packet); 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){ ogg_page audiopage; ogg_page videopage; /* 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,&td,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?theora_granule_time(&td,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, "\r %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"); #ifdef THEORA_PERF_DATA # ifdef WIN32 QueryPerformanceCounter(&final_time); elapsed_ticks = final_time.QuadPart - start_time.QuadPart; ticks_per_second; QueryPerformanceFrequency(&ticks_per_second); elapsed_secs = elapsed_ticks / ticks_per_second.QuadPart; elapsed_sec_mod = elapsed_ticks % ticks_per_second.QuadPart; elapsed_secs_dbl = elapsed_secs; elapsed_secs_dbl += ((double)elapsed_sec_mod / (double)ticks_per_second.QuadPart); printf("Encode time = %lld ticks\n", elapsed_ticks); printf("~%lld and %lld / %lld seconds\n", elapsed_secs, elapsed_sec_mod, ticks_per_second.QuadPart); printf("~%Lf seconds\n", elapsed_secs_dbl); # endif #endif return(0); }