#ifndef _MSC_VER #include #endif #include #include "quakedef.h" #include "client.h" #include "cap_ogg.h" // video capture cvars static cvar_t cl_capturevideo_ogg_theora_quality = {CVAR_SAVE, "cl_capturevideo_ogg_theora_quality", "32", "video quality factor (0 to 63), or -1 to use bitrate only; higher is better"}; static cvar_t cl_capturevideo_ogg_theora_bitrate = {CVAR_SAVE, "cl_capturevideo_ogg_theora_bitrate", "-1", "video bitrate (45 to 2000 kbps), or -1 to use quality only; higher is better"}; static cvar_t cl_capturevideo_ogg_theora_keyframe_bitrate_multiplier = {CVAR_SAVE, "cl_capturevideo_ogg_theora_keyframe_bitrate_multiplier", "1.5", "how much more bit rate to use for keyframes, specified as a factor of at least 1"}; static cvar_t cl_capturevideo_ogg_theora_keyframe_maxinterval = {CVAR_SAVE, "cl_capturevideo_ogg_theora_keyframe_maxinterval", "64", "maximum keyframe interval (1 to 1000)"}; static cvar_t cl_capturevideo_ogg_theora_keyframe_mininterval = {CVAR_SAVE, "cl_capturevideo_ogg_theora_keyframe_mininterval", "8", "minimum keyframe interval (1 to 1000)"}; static cvar_t cl_capturevideo_ogg_theora_keyframe_auto_threshold = {CVAR_SAVE, "cl_capturevideo_ogg_theora_keyframe_auto_threshold", "80", "threshold for key frame decision (0 to 100)"}; static cvar_t cl_capturevideo_ogg_theora_noise_sensitivity = {CVAR_SAVE, "cl_capturevideo_ogg_theora_noise_sensitivity", "1", "video noise sensitivity (0 to 6); lower is better"}; static cvar_t cl_capturevideo_ogg_theora_sharpness = {CVAR_SAVE, "cl_capturevideo_ogg_theora_sharpness", "0", "sharpness (0 to 2); lower is sharper"}; static cvar_t cl_capturevideo_ogg_vorbis_quality = {CVAR_SAVE, "cl_capturevideo_ogg_vorbis_quality", "3", "audio quality (-1 to 10); higher is better"}; // ogg.h stuff #ifdef _MSC_VER typedef __int16 ogg_int16_t; typedef unsigned __int16 ogg_uint16_t; typedef __int32 ogg_int32_t; typedef unsigned __int32 ogg_uint32_t; typedef __int64 ogg_int64_t; #else typedef int16_t ogg_int16_t; typedef uint16_t ogg_uint16_t; typedef int32_t ogg_int32_t; typedef uint32_t ogg_uint32_t; typedef int64_t ogg_int64_t; #endif typedef struct { long endbyte; int endbit; unsigned char *buffer; unsigned char *ptr; long storage; } oggpack_buffer; /* ogg_page is used to encapsulate the data in one Ogg bitstream page *****/ typedef struct { unsigned char *header; long header_len; unsigned char *body; long body_len; } ogg_page; /* ogg_stream_state contains the current encode/decode state of a logical Ogg bitstream **********************************************************/ typedef struct { unsigned char *body_data; /* bytes from packet bodies */ long body_storage; /* storage elements allocated */ long body_fill; /* elements stored; fill mark */ long body_returned; /* elements of fill returned */ int *lacing_vals; /* The values that will go to the segment table */ ogg_int64_t *granule_vals; /* granulepos values for headers. Not compact this way, but it is simple coupled to the lacing fifo */ long lacing_storage; long lacing_fill; long lacing_packet; long lacing_returned; unsigned char header[282]; /* working space for header encode */ int header_fill; int e_o_s; /* set when we have buffered the last packet in the logical bitstream */ int b_o_s; /* set after we've written the initial page of a logical bitstream */ long serialno; long pageno; ogg_int64_t packetno; /* sequence number for decode; the framing knows where there's a hole in the data, but we need coupling so that the codec (which is in a seperate abstraction layer) also knows about the gap */ ogg_int64_t granulepos; } ogg_stream_state; /* ogg_packet is used to encapsulate the data and metadata belonging to a single raw Ogg/Vorbis packet *************************************/ typedef struct { unsigned char *packet; long bytes; long b_o_s; long e_o_s; ogg_int64_t granulepos; ogg_int64_t packetno; /* sequence number for decode; the framing knows where there's a hole in the data, but we need coupling so that the codec (which is in a seperate abstraction layer) also knows about the gap */ } ogg_packet; typedef struct { unsigned char *data; int storage; int fill; int returned; int unsynced; int headerbytes; int bodybytes; } ogg_sync_state; /* Ogg BITSTREAM PRIMITIVES: encoding **************************/ static int (*qogg_stream_packetin) (ogg_stream_state *os, ogg_packet *op); static int (*qogg_stream_pageout) (ogg_stream_state *os, ogg_page *og); static int (*qogg_stream_flush) (ogg_stream_state *os, ogg_page *og); /* Ogg BITSTREAM PRIMITIVES: general ***************************/ static int (*qogg_stream_init) (ogg_stream_state *os,int serialno); static int (*qogg_stream_clear) (ogg_stream_state *os); static ogg_int64_t (*qogg_page_granulepos) (ogg_page *og); // end of ogg.h stuff // vorbis/codec.h stuff typedef struct vorbis_info{ int version; int channels; long rate; /* The below bitrate declarations are *hints*. Combinations of the three values carry the following implications: all three set to the same value: implies a fixed rate bitstream only nominal set: implies a VBR stream that averages the nominal bitrate. No hard upper/lower limit upper and or lower set: implies a VBR bitstream that obeys the bitrate limits. nominal may also be set to give a nominal rate. none set: the coder does not care to speculate. */ long bitrate_upper; long bitrate_nominal; long bitrate_lower; long bitrate_window; void *codec_setup; } vorbis_info; /* vorbis_dsp_state buffers the current vorbis audio analysis/synthesis state. The DSP state belongs to a specific logical bitstream ****************************************************/ typedef struct vorbis_dsp_state{ int analysisp; vorbis_info *vi; float **pcm; float **pcmret; int pcm_storage; int pcm_current; int pcm_returned; int preextrapolate; int eofflag; long lW; long W; long nW; long centerW; ogg_int64_t granulepos; ogg_int64_t sequence; ogg_int64_t glue_bits; ogg_int64_t time_bits; ogg_int64_t floor_bits; ogg_int64_t res_bits; void *backend_state; } vorbis_dsp_state; typedef struct vorbis_block{ /* necessary stream state for linking to the framing abstraction */ float **pcm; /* this is a pointer into local storage */ oggpack_buffer opb; long lW; long W; long nW; int pcmend; int mode; int eofflag; ogg_int64_t granulepos; ogg_int64_t sequence; vorbis_dsp_state *vd; /* For read-only access of configuration */ /* local storage to avoid remallocing; it's up to the mapping to structure it */ void *localstore; long localtop; long localalloc; long totaluse; struct alloc_chain *reap; /* bitmetrics for the frame */ long glue_bits; long time_bits; long floor_bits; long res_bits; void *internal; } vorbis_block; /* vorbis_block is a single block of data to be processed as part of the analysis/synthesis stream; it belongs to a specific logical bitstream, but is independant from other vorbis_blocks belonging to that logical bitstream. *************************************************/ struct alloc_chain{ void *ptr; struct alloc_chain *next; }; /* vorbis_info contains all the setup information specific to the specific compression/decompression mode in progress (eg, psychoacoustic settings, channel setup, options, codebook etc). vorbis_info and substructures are in backends.h. *********************************************************************/ /* the comments are not part of vorbis_info so that vorbis_info can be static storage */ typedef struct vorbis_comment{ /* unlimited user comment fields. libvorbis writes 'libvorbis' whatever vendor is set to in encode */ char **user_comments; int *comment_lengths; int comments; char *vendor; } vorbis_comment; /* libvorbis encodes in two abstraction layers; first we perform DSP and produce a packet (see docs/analysis.txt). The packet is then coded into a framed OggSquish bitstream by the second layer (see docs/framing.txt). Decode is the reverse process; we sync/frame the bitstream and extract individual packets, then decode the packet back into PCM audio. The extra framing/packetizing is used in streaming formats, such as files. Over the net (such as with UDP), the framing and packetization aren't necessary as they're provided by the transport and the streaming layer is not used */ /* Vorbis PRIMITIVES: general ***************************************/ static void (*qvorbis_info_init) (vorbis_info *vi); static void (*qvorbis_info_clear) (vorbis_info *vi); static void (*qvorbis_comment_init) (vorbis_comment *vc); static void (*qvorbis_comment_clear) (vorbis_comment *vc); static int (*qvorbis_block_init) (vorbis_dsp_state *v, vorbis_block *vb); static int (*qvorbis_block_clear) (vorbis_block *vb); static void (*qvorbis_dsp_clear) (vorbis_dsp_state *v); static double (*qvorbis_granule_time) (vorbis_dsp_state *v, ogg_int64_t granulepos); /* Vorbis PRIMITIVES: analysis/DSP layer ****************************/ static int (*qvorbis_analysis_init) (vorbis_dsp_state *v,vorbis_info *vi); static int (*qvorbis_commentheader_out) (vorbis_comment *vc, ogg_packet *op); static int (*qvorbis_analysis_headerout) (vorbis_dsp_state *v, vorbis_comment *vc, ogg_packet *op, ogg_packet *op_comm, ogg_packet *op_code); static float ** (*qvorbis_analysis_buffer) (vorbis_dsp_state *v,int vals); static int (*qvorbis_analysis_wrote) (vorbis_dsp_state *v,int vals); static int (*qvorbis_analysis_blockout) (vorbis_dsp_state *v,vorbis_block *vb); static int (*qvorbis_analysis) (vorbis_block *vb,ogg_packet *op); static int (*qvorbis_bitrate_addblock) (vorbis_block *vb); static int (*qvorbis_bitrate_flushpacket) (vorbis_dsp_state *vd, ogg_packet *op); // end of vorbis/codec.h stuff // vorbisenc.h stuff static int (*qvorbis_encode_init_vbr) (vorbis_info *vi, long channels, long rate, float base_quality /* quality level from 0. (lo) to 1. (hi) */ ); // end of vorbisenc.h stuff // theora.h stuff typedef struct { int y_width; /**< Width of the Y' luminance plane */ int y_height; /**< Height of the luminance plane */ int y_stride; /**< Offset in bytes between successive rows */ int uv_width; /**< Width of the Cb and Cr chroma planes */ int uv_height; /**< Height of the chroma planes */ int uv_stride; /**< Offset between successive chroma rows */ unsigned char *y; /**< Pointer to start of luminance data */ unsigned char *u; /**< Pointer to start of Cb data */ unsigned char *v; /**< Pointer to start of Cr data */ } yuv_buffer; /** * A Colorspace. */ typedef enum { OC_CS_UNSPECIFIED, /**< The colorspace is unknown or unspecified */ OC_CS_ITU_REC_470M, /**< This is the best option for 'NTSC' content */ OC_CS_ITU_REC_470BG, /**< This is the best option for 'PAL' content */ OC_CS_NSPACES /**< This marks the end of the defined colorspaces */ } theora_colorspace; /** * A Chroma subsampling * * These enumerate the available chroma subsampling options supported * by the theora format. See Section 4.4 of the specification for * exact definitions. */ typedef enum { OC_PF_420, /**< Chroma subsampling by 2 in each direction (4:2:0) */ OC_PF_RSVD, /**< Reserved value */ OC_PF_422, /**< Horizonatal chroma subsampling by 2 (4:2:2) */ OC_PF_444 /**< No chroma subsampling at all (4:4:4) */ } theora_pixelformat; /** * Theora bitstream info. * Contains the basic playback parameters for a stream, * corresponding to the initial 'info' header packet. * * Encoded theora frames must be a multiple of 16 in width and height. * To handle other frame sizes, a crop rectangle is specified in * frame_height and frame_width, offset_x and * offset_y. The offset * and size should still be a multiple of 2 to avoid chroma sampling * shifts. Offset values in this structure are measured from the * upper left of the image. * * Frame rate, in frames per second, is stored as a rational * fraction. Aspect ratio is also stored as a rational fraction, and * refers to the aspect ratio of the frame pixels, not of the * overall frame itself. * * See * examples/encoder_example.c for usage examples of the * other paramters and good default settings for the encoder parameters. */ typedef struct { ogg_uint32_t width; /**< encoded frame width */ ogg_uint32_t height; /**< encoded frame height */ ogg_uint32_t frame_width; /**< display frame width */ ogg_uint32_t frame_height; /**< display frame height */ ogg_uint32_t offset_x; /**< horizontal offset of the displayed frame */ ogg_uint32_t offset_y; /**< vertical offset of the displayed frame */ ogg_uint32_t fps_numerator; /**< frame rate numerator **/ ogg_uint32_t fps_denominator; /**< frame rate denominator **/ ogg_uint32_t aspect_numerator; /**< pixel aspect ratio numerator */ ogg_uint32_t aspect_denominator; /**< pixel aspect ratio denominator */ theora_colorspace colorspace; /**< colorspace */ int target_bitrate; /**< nominal bitrate in bits per second */ int quality; /**< Nominal quality setting, 0-63 */ int quick_p; /**< Quick encode/decode */ /* decode only */ unsigned char version_major; unsigned char version_minor; unsigned char version_subminor; void *codec_setup; /* encode only */ int dropframes_p; int keyframe_auto_p; ogg_uint32_t keyframe_frequency; ogg_uint32_t keyframe_frequency_force; /* also used for decode init to get granpos shift correct */ ogg_uint32_t keyframe_data_target_bitrate; ogg_int32_t keyframe_auto_threshold; ogg_uint32_t keyframe_mindistance; ogg_int32_t noise_sensitivity; ogg_int32_t sharpness; theora_pixelformat pixelformat; /**< chroma subsampling mode to expect */ } theora_info; /** Codec internal state and context. */ typedef struct{ theora_info *i; ogg_int64_t granulepos; void *internal_encode; void *internal_decode; } theora_state; /** * Comment header metadata. * * This structure holds the in-stream metadata corresponding to * the 'comment' header packet. * * Meta data is stored as a series of (tag, value) pairs, in * length-encoded string vectors. The first occurence of the * '=' character delimits the tag and value. A particular tag * may occur more than once. The character set encoding for * the strings is always UTF-8, but the tag names are limited * to case-insensitive ASCII. See the spec for details. * * In filling in this structure, qtheora_decode_header() will * null-terminate the user_comment strings for safety. However, * the bitstream format itself treats them as 8-bit clean, * and so the length array should be treated as authoritative * for their length. */ typedef struct theora_comment{ char **user_comments; /**< An array of comment string vectors */ int *comment_lengths; /**< An array of corresponding string vector lengths in bytes */ int comments; /**< The total number of comment string vectors */ char *vendor; /**< The vendor string identifying the encoder, null terminated */ } theora_comment; static int (*qtheora_encode_init) (theora_state *th, theora_info *ti); static int (*qtheora_encode_YUVin) (theora_state *t, yuv_buffer *yuv); static int (*qtheora_encode_packetout) ( theora_state *t, int last_p, ogg_packet *op); static int (*qtheora_encode_header) (theora_state *t, ogg_packet *op); static int (*qtheora_encode_comment) (theora_comment *tc, ogg_packet *op); static int (*qtheora_encode_tables) (theora_state *t, ogg_packet *op); static void (*qtheora_info_init) (theora_info *c); static void (*qtheora_info_clear) (theora_info *c); static void (*qtheora_clear) (theora_state *t); static void (*qtheora_comment_init) (theora_comment *tc); static void (*qtheora_comment_clear) (theora_comment *tc); static double (*qtheora_granule_time) (theora_state *th,ogg_int64_t granulepos); // end of theora.h stuff static dllfunction_t oggfuncs[] = { {"ogg_stream_packetin", (void **) &qogg_stream_packetin}, {"ogg_stream_pageout", (void **) &qogg_stream_pageout}, {"ogg_stream_flush", (void **) &qogg_stream_flush}, {"ogg_stream_init", (void **) &qogg_stream_init}, {"ogg_stream_clear", (void **) &qogg_stream_clear}, {"ogg_page_granulepos", (void **) &qogg_page_granulepos}, {NULL, NULL} }; static dllfunction_t vorbisencfuncs[] = { {"vorbis_encode_init_vbr", (void **) &qvorbis_encode_init_vbr}, {NULL, NULL} }; static dllfunction_t vorbisfuncs[] = { {"vorbis_info_init", (void **) &qvorbis_info_init}, {"vorbis_info_clear", (void **) &qvorbis_info_clear}, {"vorbis_comment_init", (void **) &qvorbis_comment_init}, {"vorbis_comment_clear", (void **) &qvorbis_comment_clear}, {"vorbis_block_init", (void **) &qvorbis_block_init}, {"vorbis_block_clear", (void **) &qvorbis_block_clear}, {"vorbis_dsp_clear", (void **) &qvorbis_dsp_clear}, {"vorbis_analysis_init", (void **) &qvorbis_analysis_init}, {"vorbis_commentheader_out", (void **) &qvorbis_commentheader_out}, {"vorbis_analysis_headerout", (void **) &qvorbis_analysis_headerout}, {"vorbis_analysis_buffer", (void **) &qvorbis_analysis_buffer}, {"vorbis_analysis_wrote", (void **) &qvorbis_analysis_wrote}, {"vorbis_analysis_blockout", (void **) &qvorbis_analysis_blockout}, {"vorbis_analysis", (void **) &qvorbis_analysis}, {"vorbis_bitrate_addblock", (void **) &qvorbis_bitrate_addblock}, {"vorbis_bitrate_flushpacket", (void **) &qvorbis_bitrate_flushpacket}, {"vorbis_granule_time", (void **) &qvorbis_granule_time}, {NULL, NULL} }; static dllfunction_t theorafuncs[] = { {"theora_info_init", (void **) &qtheora_info_init}, {"theora_info_clear", (void **) &qtheora_info_clear}, {"theora_comment_init", (void **) &qtheora_comment_init}, {"theora_comment_clear", (void **) &qtheora_comment_clear}, {"theora_encode_init", (void **) &qtheora_encode_init}, {"theora_encode_YUVin", (void **) &qtheora_encode_YUVin}, {"theora_encode_packetout", (void **) &qtheora_encode_packetout}, {"theora_encode_header", (void **) &qtheora_encode_header}, {"theora_encode_comment", (void **) &qtheora_encode_comment}, {"theora_encode_tables", (void **) &qtheora_encode_tables}, {"theora_clear", (void **) &qtheora_clear}, {"theora_granule_time", (void **) &qtheora_granule_time}, {NULL, NULL} }; static dllhandle_t og_dll = NULL, vo_dll = NULL, ve_dll = NULL, th_dll = NULL; qboolean SCR_CaptureVideo_Ogg_OpenLibrary(void) { const char* dllnames_og [] = { #if defined(WIN32) "libogg-0.dll", "libogg.dll", "ogg.dll", #elif defined(MACOSX) "libogg.dylib", #else "libogg.so.0", "libogg.so", #endif NULL }; const char* dllnames_vo [] = { #if defined(WIN32) "libvorbis-0.dll", "libvorbis.dll", "vorbis.dll", #elif defined(MACOSX) "libvorbis.dylib", #else "libvorbis.so.0", "libvorbis.so", #endif NULL }; const char* dllnames_ve [] = { #if defined(WIN32) "libvorbisenc-2.dll", "libvorbisenc.dll", "vorbisenc.dll", #elif defined(MACOSX) "libvorbisenc.dylib", #else "libvorbisenc.so.2", "libvorbisenc.so", #endif NULL }; const char* dllnames_th [] = { #if defined(WIN32) "libtheora-0.dll", "libtheora.dll", "theora.dll", #elif defined(MACOSX) "libtheora.dylib", #else "libtheora.so.0", "libtheora.so", #endif NULL }; return Sys_LoadLibrary (dllnames_og, &og_dll, oggfuncs) && Sys_LoadLibrary (dllnames_th, &th_dll, theorafuncs) && Sys_LoadLibrary (dllnames_vo, &vo_dll, vorbisfuncs) && Sys_LoadLibrary (dllnames_ve, &ve_dll, vorbisencfuncs); } void SCR_CaptureVideo_Ogg_Init(void) { SCR_CaptureVideo_Ogg_OpenLibrary(); Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_quality); Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_bitrate); Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_keyframe_bitrate_multiplier); Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_keyframe_maxinterval); Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_keyframe_mininterval); Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_keyframe_auto_threshold); Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_noise_sensitivity); Cvar_RegisterVariable(&cl_capturevideo_ogg_vorbis_quality); } qboolean SCR_CaptureVideo_Ogg_Available(void) { return og_dll && th_dll && vo_dll && ve_dll; } void SCR_CaptureVideo_Ogg_CloseDLL(void) { Sys_UnloadLibrary (&ve_dll); Sys_UnloadLibrary (&vo_dll); Sys_UnloadLibrary (&th_dll); Sys_UnloadLibrary (&og_dll); } // this struct should not be needed // however, libogg appears to pull the ogg_page's data element away from our // feet before we get to write the data due to interleaving // so this struct is used to keep the page data around until it actually gets // written typedef struct allocatedoggpage_s { size_t len; double time; unsigned char data[65307]; // this number is from RFC 3533. In case libogg writes more, we'll have to increase this // but we'll get a Host_Error in this case so we can track it down } allocatedoggpage_t; typedef struct capturevideostate_ogg_formatspecific_s { ogg_stream_state to, vo; int serial1, serial2; theora_state ts; vorbis_dsp_state vd; vorbis_block vb; vorbis_info vi; yuv_buffer yuv[2]; int yuvi; int lastnum; int channels; allocatedoggpage_t videopage, audiopage; } capturevideostate_ogg_formatspecific_t; #define LOAD_FORMATSPECIFIC_OGG() capturevideostate_ogg_formatspecific_t *format = (capturevideostate_ogg_formatspecific_t *) cls.capturevideo.formatspecific static void SCR_CaptureVideo_Ogg_Interleave(void) { LOAD_FORMATSPECIFIC_OGG(); ogg_page pg; if(!cls.capturevideo.soundrate) { while(qogg_stream_pageout(&format->to, &pg) > 0) { FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); } return; } for(;;) { // first: make sure we have a page of both types if(!format->videopage.len) if(qogg_stream_pageout(&format->to, &pg) > 0) { format->videopage.len = pg.header_len + pg.body_len; format->videopage.time = qtheora_granule_time(&format->ts, qogg_page_granulepos(&pg)); if(format->videopage.len > sizeof(format->videopage.data)) Host_Error("video page too long"); memcpy(format->videopage.data, pg.header, pg.header_len); memcpy(format->videopage.data + pg.header_len, pg.body, pg.body_len); } if(!format->audiopage.len) if(qogg_stream_pageout(&format->vo, &pg) > 0) { format->audiopage.len = pg.header_len + pg.body_len; format->audiopage.time = qvorbis_granule_time(&format->vd, qogg_page_granulepos(&pg)); if(format->audiopage.len > sizeof(format->audiopage.data)) Host_Error("audio page too long"); memcpy(format->audiopage.data, pg.header, pg.header_len); memcpy(format->audiopage.data + pg.header_len, pg.body, pg.body_len); } if(format->videopage.len && format->audiopage.len) { // output the page that ends first if(format->videopage.time < format->audiopage.time) { FS_Write(cls.capturevideo.videofile, format->videopage.data, format->videopage.len); format->videopage.len = 0; } else { FS_Write(cls.capturevideo.videofile, format->audiopage.data, format->audiopage.len); format->audiopage.len = 0; } } else break; } } static void SCR_CaptureVideo_Ogg_FlushInterleaving(void) { LOAD_FORMATSPECIFIC_OGG(); if(cls.capturevideo.soundrate) if(format->audiopage.len) { FS_Write(cls.capturevideo.videofile, format->audiopage.data, format->audiopage.len); format->audiopage.len = 0; } if(format->videopage.len) { FS_Write(cls.capturevideo.videofile, format->videopage.data, format->videopage.len); format->videopage.len = 0; } } static void SCR_CaptureVideo_Ogg_EndVideo(void) { LOAD_FORMATSPECIFIC_OGG(); ogg_page pg; ogg_packet pt; if(format->yuvi >= 0) { // send the previous (and last) frame while(format->lastnum-- > 0) { qtheora_encode_YUVin(&format->ts, &format->yuv[format->yuvi]); while(qtheora_encode_packetout(&format->ts, !format->lastnum, &pt)) qogg_stream_packetin(&format->to, &pt); SCR_CaptureVideo_Ogg_Interleave(); } } if(cls.capturevideo.soundrate) { qvorbis_analysis_wrote(&format->vd, 0); while(qvorbis_analysis_blockout(&format->vd, &format->vb) == 1) { qvorbis_analysis(&format->vb, NULL); qvorbis_bitrate_addblock(&format->vb); while(qvorbis_bitrate_flushpacket(&format->vd, &pt)) qogg_stream_packetin(&format->vo, &pt); SCR_CaptureVideo_Ogg_Interleave(); } } SCR_CaptureVideo_Ogg_FlushInterleaving(); while(qogg_stream_pageout(&format->to, &pg) > 0) { FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); } if(cls.capturevideo.soundrate) { while(qogg_stream_pageout(&format->vo, &pg) > 0) { FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); } } while (1) { int result = qogg_stream_flush (&format->to, &pg); if (result < 0) fprintf (stderr, "Internal Ogg library error.\n"); // TODO Host_Error if (result <= 0) break; FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); } if(cls.capturevideo.soundrate) { while (1) { int result = qogg_stream_flush (&format->vo, &pg); if (result < 0) fprintf (stderr, "Internal Ogg library error.\n"); // TODO Host_Error if (result <= 0) break; FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); } qogg_stream_clear(&format->vo); qvorbis_block_clear(&format->vb); qvorbis_dsp_clear(&format->vd); } qogg_stream_clear(&format->to); qtheora_clear(&format->ts); qvorbis_info_clear(&format->vi); Mem_Free(format->yuv[0].y); Mem_Free(format->yuv[0].u); Mem_Free(format->yuv[0].v); Mem_Free(format->yuv[1].y); Mem_Free(format->yuv[1].u); Mem_Free(format->yuv[1].v); Mem_Free(format); FS_Close(cls.capturevideo.videofile); cls.capturevideo.videofile = NULL; } static void SCR_CaptureVideo_Ogg_ConvertFrame_BGRA_to_YUV(void) { LOAD_FORMATSPECIFIC_OGG(); yuv_buffer *yuv; int x, y; int blockr, blockg, blockb; unsigned char *b = cls.capturevideo.outbuffer; int w = cls.capturevideo.width; int h = cls.capturevideo.height; int inpitch = w*4; yuv = &format->yuv[format->yuvi]; for(y = 0; y < h; ++y) { for(b = cls.capturevideo.outbuffer + (h-1-y)*w*4, x = 0; x < w; ++x) { blockr = b[2]; blockg = b[1]; blockb = b[0]; yuv->y[x + yuv->y_stride * y] = cls.capturevideo.yuvnormalizetable[0][cls.capturevideo.rgbtoyuvscaletable[0][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[0][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[0][2][blockb]]; b += 4; } if((y & 1) == 0) { for(b = cls.capturevideo.outbuffer + (h-2-y)*w*4, x = 0; x < w/2; ++x) { blockr = (b[2] + b[6] + b[inpitch+2] + b[inpitch+6]) >> 2; blockg = (b[1] + b[5] + b[inpitch+1] + b[inpitch+5]) >> 2; blockb = (b[0] + b[4] + b[inpitch+0] + b[inpitch+4]) >> 2; yuv->u[x + yuv->uv_stride * (y/2)] = cls.capturevideo.yuvnormalizetable[1][cls.capturevideo.rgbtoyuvscaletable[1][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[1][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[1][2][blockb] + 128]; yuv->v[x + yuv->uv_stride * (y/2)] = cls.capturevideo.yuvnormalizetable[2][cls.capturevideo.rgbtoyuvscaletable[2][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[2][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[2][2][blockb] + 128]; b += 8; } } } } static void SCR_CaptureVideo_Ogg_VideoFrames(int num) { LOAD_FORMATSPECIFIC_OGG(); ogg_packet pt; // data is in cls.capturevideo.outbuffer as BGRA and has size width*height if(format->yuvi >= 0) { // send the previous frame while(format->lastnum-- > 0) { qtheora_encode_YUVin(&format->ts, &format->yuv[format->yuvi]); while(qtheora_encode_packetout(&format->ts, false, &pt)) qogg_stream_packetin(&format->to, &pt); SCR_CaptureVideo_Ogg_Interleave(); } } format->yuvi = (format->yuvi + 1) % 2; SCR_CaptureVideo_Ogg_ConvertFrame_BGRA_to_YUV(); format->lastnum = num; // TODO maybe send num-1 frames from here already } typedef int channelmapping_t[8]; channelmapping_t mapping[8] = { { 0, -1, -1, -1, -1, -1, -1, -1 }, // mono { 0, 1, -1, -1, -1, -1, -1, -1 }, // stereo { 0, 1, 2, -1, -1, -1, -1, -1 }, // L C R { 0, 1, 2, 3, -1, -1, -1, -1 }, // surround40 { 0, 2, 3, 4, 1, -1, -1, -1 }, // FL FC FR RL RR { 0, 2, 3, 4, 1, 5, -1, -1 }, // surround51 { 0, 2, 3, 4, 1, 5, 6, -1 }, // (not defined by vorbis spec) { 0, 2, 3, 4, 1, 5, 6, 7 } // surround71 (not defined by vorbis spec) }; static void SCR_CaptureVideo_Ogg_SoundFrame(const portable_sampleframe_t *paintbuffer, size_t length) { LOAD_FORMATSPECIFIC_OGG(); float **vorbis_buffer; size_t i; int j; ogg_packet pt; int *map = mapping[bound(1, cls.capturevideo.soundchannels, 8) - 1]; vorbis_buffer = qvorbis_analysis_buffer(&format->vd, length); for(j = 0; j < cls.capturevideo.soundchannels; ++j) { float *b = vorbis_buffer[map[j]]; for(i = 0; i < length; ++i) b[i] = paintbuffer[i].sample[j] / 32768.0f; } qvorbis_analysis_wrote(&format->vd, length); while(qvorbis_analysis_blockout(&format->vd, &format->vb) == 1) { qvorbis_analysis(&format->vb, NULL); qvorbis_bitrate_addblock(&format->vb); while(qvorbis_bitrate_flushpacket(&format->vd, &pt)) qogg_stream_packetin(&format->vo, &pt); } SCR_CaptureVideo_Ogg_Interleave(); } void SCR_CaptureVideo_Ogg_BeginVideo(void) { cls.capturevideo.format = CAPTUREVIDEOFORMAT_OGG_VORBIS_THEORA; cls.capturevideo.formatextension = "ogv"; cls.capturevideo.videofile = FS_OpenRealFile(va("%s.%s", cls.capturevideo.basename, cls.capturevideo.formatextension), "wb", false); cls.capturevideo.endvideo = SCR_CaptureVideo_Ogg_EndVideo; cls.capturevideo.videoframes = SCR_CaptureVideo_Ogg_VideoFrames; cls.capturevideo.soundframe = SCR_CaptureVideo_Ogg_SoundFrame; cls.capturevideo.formatspecific = Mem_Alloc(tempmempool, sizeof(capturevideostate_ogg_formatspecific_t)); { LOAD_FORMATSPECIFIC_OGG(); int num, denom, i; ogg_page pg; ogg_packet pt, pt2, pt3; theora_comment tc; vorbis_comment vc; theora_info ti; format->serial1 = rand(); qogg_stream_init(&format->to, format->serial1); if(cls.capturevideo.soundrate) { do { format->serial2 = rand(); } while(format->serial1 == format->serial2); qogg_stream_init(&format->vo, format->serial2); } format->videopage.len = format->audiopage.len = 0; qtheora_info_init(&ti); ti.frame_width = cls.capturevideo.width; ti.frame_height = cls.capturevideo.height; ti.width = (ti.frame_width + 15) & ~15; ti.height = (ti.frame_height + 15) & ~15; //ti.offset_x = ((ti.width - ti.frame_width) / 2) & ~1; //ti.offset_y = ((ti.height - ti.frame_height) / 2) & ~1; for(i = 0; i < 2; ++i) { format->yuv[i].y_width = ti.width; format->yuv[i].y_height = ti.height; format->yuv[i].y_stride = ti.width; format->yuv[i].uv_width = ti.width / 2; format->yuv[i].uv_height = ti.height / 2; format->yuv[i].uv_stride = ti.width / 2; format->yuv[i].y = (unsigned char *) Mem_Alloc(tempmempool, format->yuv[i].y_stride * format->yuv[i].y_height); format->yuv[i].u = (unsigned char *) Mem_Alloc(tempmempool, format->yuv[i].uv_stride * format->yuv[i].uv_height); format->yuv[i].v = (unsigned char *) Mem_Alloc(tempmempool, format->yuv[i].uv_stride * format->yuv[i].uv_height); } format->yuvi = -1; // -1: no frame valid yet, write into 0 FindFraction(cls.capturevideo.framerate / cls.capturevideo.framestep, &num, &denom, 1001); ti.fps_numerator = num; ti.fps_denominator = denom; FindFraction(1 / vid_pixelheight.value, &num, &denom, 1000); ti.aspect_numerator = num; ti.aspect_denominator = denom; ti.colorspace = OC_CS_UNSPECIFIED; ti.pixelformat = OC_PF_420; ti.quick_p = true; // http://mlblog.osdir.com/multimedia.ogg.theora.general/2004-07/index.shtml ti.dropframes_p = false; ti.target_bitrate = cl_capturevideo_ogg_theora_bitrate.integer * 1000; ti.quality = cl_capturevideo_ogg_theora_quality.integer; if(ti.target_bitrate <= 0) { if(ti.quality < 0) { ti.target_bitrate = -1; ti.keyframe_data_target_bitrate = (unsigned int)-1; ti.quality = 63; } else { ti.target_bitrate = -1; ti.keyframe_data_target_bitrate = (unsigned int)-1; ti.quality = bound(0, ti.quality, 63); } } else { if(ti.quality < 0) { ti.target_bitrate = bound(45000, ti.target_bitrate, 2000000); ti.keyframe_data_target_bitrate = (int) (ti.target_bitrate * max(1, cl_capturevideo_ogg_theora_keyframe_bitrate_multiplier.value)); ti.quality = -1; } else { ti.target_bitrate = bound(45000, ti.target_bitrate, 2000000); ti.keyframe_data_target_bitrate = (int) (ti.target_bitrate * max(1, cl_capturevideo_ogg_theora_keyframe_bitrate_multiplier.value)); ti.quality = -1; } } // this -1 magic is because ti.keyframe_frequency and ti.keyframe_mindistance use different metrics ti.keyframe_frequency = bound(1, cl_capturevideo_ogg_theora_keyframe_maxinterval.integer, 1000); ti.keyframe_mindistance = bound(1, cl_capturevideo_ogg_theora_keyframe_mininterval.integer, (int) ti.keyframe_frequency) - 1; ti.noise_sensitivity = bound(0, cl_capturevideo_ogg_theora_noise_sensitivity.integer, 6); ti.sharpness = bound(0, cl_capturevideo_ogg_theora_sharpness.integer, 2); ti.keyframe_auto_threshold = bound(0, cl_capturevideo_ogg_theora_keyframe_auto_threshold.integer, 100); ti.keyframe_frequency_force = ti.keyframe_frequency; ti.keyframe_auto_p = (ti.keyframe_frequency != ti.keyframe_mindistance + 1); qtheora_encode_init(&format->ts, &ti); qtheora_info_clear(&ti); // vorbis? if(cls.capturevideo.soundrate) { qvorbis_info_init(&format->vi); qvorbis_encode_init_vbr(&format->vi, cls.capturevideo.soundchannels, cls.capturevideo.soundrate, bound(-1, cl_capturevideo_ogg_vorbis_quality.value, 10) * 0.099); qvorbis_comment_init(&vc); qvorbis_analysis_init(&format->vd, &format->vi); qvorbis_block_init(&format->vd, &format->vb); } qtheora_comment_init(&tc); /* create the remaining theora headers */ qtheora_encode_header(&format->ts, &pt); qogg_stream_packetin(&format->to, &pt); if (qogg_stream_pageout (&format->to, &pg) != 1) fprintf (stderr, "Internal Ogg library error.\n"); FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); qtheora_encode_comment(&tc, &pt); qogg_stream_packetin(&format->to, &pt); qtheora_encode_tables(&format->ts, &pt); qogg_stream_packetin (&format->to, &pt); qtheora_comment_clear(&tc); if(cls.capturevideo.soundrate) { qvorbis_analysis_headerout(&format->vd, &vc, &pt, &pt2, &pt3); qogg_stream_packetin(&format->vo, &pt); if (qogg_stream_pageout (&format->vo, &pg) != 1) fprintf (stderr, "Internal Ogg library error.\n"); FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); qogg_stream_packetin(&format->vo, &pt2); qogg_stream_packetin(&format->vo, &pt3); qvorbis_comment_clear(&vc); } for(;;) { int result = qogg_stream_flush (&format->to, &pg); if (result < 0) fprintf (stderr, "Internal Ogg library error.\n"); // TODO Host_Error if (result <= 0) break; FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); } if(cls.capturevideo.soundrate) for(;;) { int result = qogg_stream_flush (&format->vo, &pg); if (result < 0) fprintf (stderr, "Internal Ogg library error.\n"); // TODO Host_Error if (result <= 0) break; FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); } } }