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.

779 lines
19 KiB

/*
* export_divx5.c
*
* Copyright (C) Thomas Oestreich - June 2001
*
* 2-pass code OpenDivX port: "-R 1", "-R 2"
* Copyright (C) 2001 Christoph Lampert <gruel@web.de>
*
* constant quantizer extensions "-R 3" by Gerhard Monzel
* <gerhard.monzel@sap.com>
*
* This module is derived from export_divx4.c, minor modification by
* Christoph Lampert <gruel@web.de>
*
* This file is part of transcode, a video stream processing tool
*
* transcode is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* transcode is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GNU Make; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <math.h>
#include <stdint.h>
#ifdef HAVE_DLFCN_H
#include <dlfcn.h>
#else
# ifdef OS_DARWIN
# include "libdldarwin/dlfcn.h"
# endif
#endif
#ifdef HAVE_ENCORE2_H
#include <encore2.h>
#else
#include "divx5_encore2.h"
#endif
#include "transcode.h"
#include "avilib/avilib.h"
#include "aud_aux.h"
#include "vbr.h"
#define MOD_NAME "export_divx5.so"
#define MOD_VERSION "v0.1.8 (2003-07-24)"
#define MOD_CODEC "(video) DivX 5.xx | (audio) MPEG/AC3/PCM"
static int verbose_flag=TC_QUIET;
static int capability_flag=TC_CAP_PCM|TC_CAP_RGB|TC_CAP_YUV|TC_CAP_AC3|TC_CAP_AUD;
#define MOD_PRE divx5
#include "export_def.h"
int VbrMode=0;
int force_key_frame=-1;
static avi_t *avifile=NULL;
//temporary audio/video buffer
static char *buffer;
#if ENCORE_VERSION >= 20021024
DivXBitmapInfoHeader *format =NULL;
void* encore_handle = NULL;
SETTINGS *settings = NULL;
char *logfile_mv=NULL;
#else
ENC_PARAM *divx;
#endif
ENC_FRAME encode;
ENC_RESULT key;
// dl stuff
static int (*divx5_encore)(void *para0, int opt, void *para1, void *para2);
static void *handle;
static char module[TC_BUF_MAX];
#if ENCORE_VERSION >= 20021024
static char * prof2name(int n)
{
switch (n) {
case 0: return "Free/No profile";
case 1: return "Handheld";
case 2: return "Portable";
case 3: return "Home Theatre";
case 4: return "High Definition";
default: return "Free/No profile";
}
}
#endif
#define MODULE "libdivxencore.so.0"
static int divx5_init(const char *path) {
const char *error;
int *quiet_encore;
tc_snprintf(module, sizeof(module), "%s/%s", path, MODULE);
// try transcode's module directory
handle = dlopen(module, RTLD_NOW);
if (!handle) {
//try the default:
handle = dlopen(MODULE, RTLD_GLOBAL| RTLD_LAZY);
if (!handle) {
tc_log_warn(MOD_NAME, "%s", dlerror());
return(-1);
} else {
if(verbose_flag & TC_DEBUG)
tc_log_info(MOD_NAME, "Loading external codec module %s", MODULE);
}
} else {
if(verbose_flag & TC_DEBUG)
tc_log_info(MOD_NAME, "Loading external codec module %s", module);
}
divx5_encore = dlsym(handle, "encore");
if ((error = dlerror()) != NULL) {
tc_log_warn(MOD_NAME, "%s", error);
return(-1);
}
quiet_encore=dlsym(handle, "quiet_encore");
if ((error = dlerror()) != NULL) {
tc_log_warn(MOD_NAME, "%s", error);
return(-1);
}
*quiet_encore=1;
// debug
if(verbose_flag & TC_STATS) *quiet_encore=0;
return(0);
}
/* ------------------------------------------------------------
*
* init codec
*
* ------------------------------------------------------------*/
MOD_init
{
#if ENCORE_VERSION >= 20021024
#else
struct stat fbuf;
#endif
int ch;
tc_log_warn(MOD_NAME, "*** Warning: DivX is broken and support for it is ***");
tc_log_warn(MOD_NAME, "*** obsolete in transcode. Sooner or later it ***");
tc_log_warn(MOD_NAME, "*** will be removed from transcode. Don't use ***");
tc_log_warn(MOD_NAME, "*** DivX. Use xvid or ffmpeg -F mpeg4 instead ***");
tc_log_warn(MOD_NAME, "*** for all your mpeg4 encodings. ***");
if(param->flag == TC_VIDEO) {
//check for odd frame parameter:
if((ch = vob->ex_v_width - ((vob->ex_v_width>>3)<<3)) != 0) {
tc_log_warn(MOD_NAME, "frame width %d (no multiple of 8)", vob->ex_v_width);
tc_log_warn(MOD_NAME, "encoder may not work correctly or crash");
if(ch & 1) {
tc_log_warn(MOD_NAME, "invalid frame width");
return(TC_EXPORT_ERROR);
}
}
if((ch = vob->ex_v_height - ((vob->ex_v_height>>3)<<3)) != 0) {
tc_log_warn(MOD_NAME, "frame height %d (no multiple of 8)", vob->ex_v_height);
tc_log_warn(MOD_NAME, "encoder may not work correctly or crash");
if(ch & 1) {
tc_log_warn(MOD_NAME, "invalid frame height");
return(TC_EXPORT_ERROR);
}
}
if ((buffer = malloc(vob->ex_v_height*vob->ex_v_width*3))==NULL) {
tc_log_perror(MOD_NAME, "out of memory");
return(TC_EXPORT_ERROR);
} else
memset(buffer, 0, vob->ex_v_height*vob->ex_v_width*3);
//load the codec
if(divx5_init(vob->mod_path)<0) {
tc_log_warn(MOD_NAME, "failed to init DivX 5.0 Codec");
return(TC_EXPORT_ERROR);
}
if (divx5_encore(0, ENC_OPT_VERSION, 0, 0) != ENCORE_VERSION) {
tc_log_warn(MOD_NAME, "API in encore.h is not compatible with installed lbdivxencore library");
return (TC_EXPORT_ERROR);
}
VbrMode = vob->divxmultipass;
// 0 for nothing,
// 1 for DivX 5.0 - first-pass,
// 2 for DivX 5.0 - second pass
// 3 constant quantizer
#if ENCORE_VERSION >= 20021024
#define FOURCC(A, B, C, D) ( ((uint8_t) (A)) | (((uint8_t) (B))<<8) | (((uint8_t) (C))<<16) | (((uint8_t) (D))<<24) )
if ((settings = malloc(sizeof(SETTINGS)))==NULL) {
tc_log_perror(MOD_NAME, "out of memory");
return(TC_EXPORT_ERROR);
}
if ((format = malloc(sizeof(DivXBitmapInfoHeader)))==NULL) {
tc_log_perror(MOD_NAME, "out of memory");
return(TC_EXPORT_ERROR);
}
memset (settings, 0, sizeof(SETTINGS));
memset (format, 0, sizeof(DivXBitmapInfoHeader));
format->biSize = sizeof(DivXBitmapInfoHeader);
format->biWidth = vob->ex_v_width;
format->biHeight = vob->ex_v_height;
format->biCompression = (vob->im_v_codec==CODEC_RGB)?0:FOURCC('Y','V','1','2');
format->biBitCount = (vob->im_v_codec==CODEC_RGB)?24:0;
switch (vob->ex_frc) {
case 1: // 23.976
settings->input_clock = 24000;
settings->input_frame_period = 1001;
break;
case 2: // 24.000
settings->input_clock = 24000;
settings->input_frame_period = 1000;
break;
case 3: // 25.000
settings->input_clock = 25000;
settings->input_frame_period = 1000;
break;
case 4: // 29.970
settings->input_clock = 30000;
settings->input_frame_period = 1001;
break;
case 5: // 30.000
settings->input_clock = 30000;
settings->input_frame_period = 1000;
break;
case 0: // notset
default:
settings->input_clock = (int)vob->ex_fps*1000;
settings->input_frame_period = 1000;
break;
}
if (vob->divxlogfile && *vob->divxlogfile) {
if ((logfile_mv = malloc (strlen(vob->divxlogfile)+4)) == NULL) {
tc_log_error(MOD_NAME, "Cannot allocate memory for logfile_mv");
return(TC_EXPORT_ERROR);
}
tc_snprintf(logfile_mv, strlen(vob->divxlogfile)+4, "%s_mv", vob->divxlogfile);
}
// default -- expose this to user?
settings->complexity_modulation = 0.5;
settings->bitrate = vob->divxbitrate*1000;
settings->max_key_interval = vob->divxkeyframes;
settings->quality = vob->divxquality;
if (VbrMode == 1 || VbrMode == 2){
/*
* http://www.divx.com/support/divx/guide_mac.php
*
* Handheld 128000,262144,196608
* Portable 768000,1048576,786432
* Home Theatre Theatre 4000000,3145728,2359296
* High Definition 8000000,6291456,4718592
*/
switch (vob->divx5_vbv_prof) {
case 1: // Handheld
settings->vbv_bitrate = 128000;
settings->vbv_size = 262144;
settings->vbv_occupancy = 196608;
break;
case 2: // Portable
settings->vbv_bitrate = 768000;
settings->vbv_size = 1048576;
settings->vbv_occupancy = 786432;
break;
case 3: // Home Theatre
settings->vbv_bitrate = 4000000;
settings->vbv_size = 3145728;
settings->vbv_occupancy = 2359296;
break;
case 4: // High Definition
settings->vbv_bitrate = 8000000;
settings->vbv_size = 6291456;
settings->vbv_occupancy = 4718592;
break;
case 0: // Free/user supplied
default:
settings->vbv_bitrate = vob->divx5_vbv_bitrate*400;
settings->vbv_size = vob->divx5_vbv_size*16384;
settings->vbv_occupancy = vob->divx5_vbv_occupancy*64;
break;
}
if (verbose & TC_DEBUG)
tc_log_info(MOD_NAME, "Using VBV Profile [%d] (%s)",
vob->divx5_vbv_prof,
prof2name(vob->divx5_vbv_prof));
}
switch(VbrMode) {
case 0:
break;
settings->vbr_mode = RCMODE_VBV_1PASS;
case 1:
settings->vbr_mode = RCMODE_VBV_MULTIPASS_1ST;
settings->mv_file = logfile_mv;
settings->log_file_read = NULL;
settings->log_file_write = vob->divxlogfile;
break;
case 2:
settings->vbr_mode = RCMODE_VBV_MULTIPASS_NTH;
settings->mv_file = logfile_mv;
settings->log_file_read = vob->divxlogfile;
// segfaults if !NULL;
settings->log_file_write = NULL;
break;
case 3:
settings->vbr_mode = RCMODE_1PASS_CONSTANT_Q;
settings->quantizer = vob->divxbitrate;
break;
}
// bframes .. lets see how to handle it
// the codec is crippled anyway
settings->use_bidirect = 0;
// don't need this.
settings->enable_crop = 0;
settings->enable_resize = 0;
if(divx5_encore(&encore_handle, ENC_OPT_INIT, format, settings) < 0) {
tc_log_warn(MOD_NAME, "Error doing ENC_OPT_INIT");
return(TC_EXPORT_ERROR);
}
#else
if ((divx = malloc(sizeof(ENC_PARAM)))==NULL) {
tc_log_perror(MOD_NAME, "out of memory");
return(TC_EXPORT_ERROR);
}
memset(divx, 0, sizeof(ENC_PARAM));
//important parameter (Note: use_bidirect and obmc have been removed since DivX4)
divx->x_dim = vob->ex_v_width;
divx->y_dim = vob->ex_v_height;
divx->framerate = vob->ex_fps;
divx->bitrate = vob->divxbitrate*1000;
//recommended (advanced) parameter
divx->min_quantizer = vob->min_quantizer;
divx->max_quantizer = vob->max_quantizer;
divx->rc_period = vob->rc_period;
divx->rc_reaction_period = vob->rc_reaction_period;
divx->rc_reaction_ratio = vob->rc_reaction_ratio;
divx->max_key_interval = vob->divxkeyframes;
divx->quality = vob->divxquality;
divx->deinterlace=(vob->deinterlace==2) ? 1:0; // fast deinterlace = 1
divx->handle=NULL;
if(divx5_encore(NULL, ENC_OPT_INIT, divx, NULL) < 0) {
tc_log_warn(MOD_NAME, "DivX codec init error");
return(TC_EXPORT_ERROR);
}
// catch API mismatch
if(!divx || !divx->handle) {
tc_log_warn(MOD_NAME, "DivX codec open error");
return(TC_EXPORT_ERROR);
}
if(verbose_flag & TC_DEBUG)
{
//-- GMO start --
if (vob->divxmultipass == 3) {
tc_log_info(MOD_NAME, " single-pass session: %d (VBR)", vob->divxmultipass);
tc_log_info(MOD_NAME, " VBR-quantizer: %d", vob->divxbitrate);
} else {
tc_log_info(MOD_NAME, " multi-pass session: %d", vob->divxmultipass);
tc_log_info(MOD_NAME, " bitrate [kBits/s]: %d", divx->bitrate/1000);
}
tc_log_info(MOD_NAME, " quality: %d", divx->quality);
//-- GMO end --
tc_log_info(MOD_NAME, " crispness: %d", vob->divxcrispness);
tc_log_info(MOD_NAME, " max keyframe interval: %d", divx->max_key_interval);
tc_log_info(MOD_NAME, " frame rate: %.2f", vob->ex_fps);
tc_log_info(MOD_NAME, " color space: %s", (vob->im_v_codec==CODEC_RGB) ? "RGB24" : "YUV420P");
tc_log_info(MOD_NAME, " deinterlace: %d", divx->deinterlace);
}
encode.colorspace = (vob->im_v_codec==CODEC_RGB) ? ENC_CSP_RGB24:ENC_CSP_I420;
encode.mvs = NULL;
encode.bitstream = buffer;
switch(VbrMode) {
case 1:
VbrControl_init_2pass_vbr_analysis(vob->divxlogfile, divx->quality);
break;
case 2:
// check for logfile
if(vob->divxlogfile==NULL || stat(vob->divxlogfile, &fbuf)){
tc_log_warn(MOD_NAME, "pass-1 logfile \"%s\" not found exit",
vob->divxlogfile);
return(TC_EXPORT_ERROR);
}
// second pass: read back the logfile
VbrControl_init_2pass_vbr_encoding(vob->divxlogfile,
divx->bitrate,
divx->framerate,
vob->divxcrispness,
divx->quality);
break;
//-- GMO start --
case 3:
VbrControl_init_2pass_vbr_analysis(vob->divxlogfile, divx->quality);
encode.quant = vob->divxbitrate;
encode.intra = -1;
break;
//-- GMO end --
default:
// none
break;
}
#endif
return(TC_EXPORT_OK);
}
if(param->flag == TC_AUDIO) {
tc_log_warn(MOD_NAME, "Usage of this module for audio encoding is deprecated.");
tc_log_warn(MOD_NAME, "Consider switch to export_tcaud module.");
return(tc_audio_init(vob, verbose));
}
// invalid flag
return(TC_EXPORT_ERROR);
}
/* ------------------------------------------------------------
*
* open outputfile
*
* ------------------------------------------------------------*/
MOD_open
{
// open file
if(vob->avifile_out==NULL)
if(NULL == (vob->avifile_out = AVI_open_output_file(vob->video_out_file))) {
AVI_print_error("avi open error");
return(TC_EXPORT_ERROR);
}
/* save locally */
avifile = vob->avifile_out;
if(param->flag == TC_AUDIO) return(tc_audio_open(vob, vob->avifile_out));
if(param->flag == TC_VIDEO) {
// video
#if ENCORE_MAJOR_VERSION >= 5010
AVI_set_video(vob->avifile_out, vob->ex_v_width, vob->ex_v_height,
vob->ex_fps, "DX50");
#else
AVI_set_video(vob->avifile_out, vob->ex_v_width, vob->ex_v_height,
vob->ex_fps, "DIVX");
#endif
if (vob->avi_comment_fd>0)
AVI_set_comment_fd(vob->avifile_out, vob->avi_comment_fd);
//do not force key frame at the very beginning of encoding, since
//first frame will be a key fame anayway. Therefore key.quantizer
//is well defined for any frame to follow
force_key_frame=(force_key_frame<0) ? 0:1;
return(0);
}
// invalid flag
return(TC_EXPORT_ERROR);
}
/* ------------------------------------------------------------
*
* encode and export frame
*
* ------------------------------------------------------------*/
MOD_encode
{
if(param->flag == TC_VIDEO) {
// encode video
encode.image = param->buffer;
encode.bitstream = buffer;
#if ENCORE_VERSION >= 20021024
encode.produce_empty_frame = 0;
do {
if(divx5_encore(encore_handle, ENC_OPT_ENCODE, &encode, &key) < 0) {
tc_log_warn(MOD_NAME, "DivX encoder error");
return(TC_EXPORT_ERROR);
}
// write bitstream
if(key.cType != '\0') {
/* split the AVI */
if((uint32_t)(AVI_bytes_written(avifile)+encode.length+16+8)>>20 >= tc_avi_limit)
tc_outstream_rotate_request();
//0.6.2: switch outfile on "C" and -J pv
if(key.cType == 'I') tc_outstream_rotate();
if(AVI_write_frame(avifile, buffer, encode.length, (key.cType == 'I')?1:0)<0) {
tc_log_warn(MOD_NAME, "DivX avi video write error");
return(TC_EXPORT_ERROR);
}
}
encode.image = NULL;
} while (encode.length >= 0 && key.cType != '\0');
#else
switch(VbrMode) {
//-- GMO start --
case 3:
if (force_key_frame)
{
encode.intra = 1;
force_key_frame = 0;
}
else
encode.intra = -1;
if(divx5_encore(divx->handle, ENC_OPT_ENCODE_VBR, &encode, &key) < 0)
{
tc_log_warn(MOD_NAME, "encoder error");
return(TC_EXPORT_ERROR);
}
VbrControl_update_2pass_vbr_analysis(key.is_key_frame,
key.motion_bits,
key.texture_bits,
key.total_bits,
key.quantizer);
break;
//-- GMO end --
case 2:
// second pass of 2-pass, just a hack for the moment
encode.quant = VbrControl_get_quant();
encode.intra = VbrControl_get_intra();
if(force_key_frame) {
encode.intra=1; //key frame
force_key_frame=0; //reset
}
if(divx5_encore(divx->handle, ENC_OPT_ENCODE_VBR, &encode, &key) < 0) {
tc_log_warn(MOD_NAME, "encoder error");
return(TC_EXPORT_ERROR);
}
VbrControl_update_2pass_vbr_encoding(key.motion_bits,
key.texture_bits,
key.total_bits);
break;
default:
if(force_key_frame) {
encode.intra=1; //key frame
encode.quant=key.quantizer; //well defined for frames != first frame.
if(divx5_encore(divx->handle, ENC_OPT_ENCODE_VBR, &encode, &key) < 0) {
tc_log_warn(MOD_NAME, "encoder error");
return(TC_EXPORT_ERROR);
}
//reset
force_key_frame=0;
} else {
if(divx5_encore(divx->handle, ENC_OPT_ENCODE, &encode, &key) < 0) {
tc_log_warn(MOD_NAME, "encoder error");
return(TC_EXPORT_ERROR);
}
}
// first pass of two-pass, save results
if(VbrMode==1)
VbrControl_update_2pass_vbr_analysis(key.is_key_frame,
key.motion_bits,
key.texture_bits,
key.total_bits,
key.quantizer);
break;
}
// write bitstream
/* split the AVI */
if((uint32_t)(AVI_bytes_written(avifile)+encode.length+16+8)>>20 >= tc_avi_limit)
tc_outstream_rotate_request();
//0.6.2: switch outfile on "C" and -J pv
if(key.is_key_frame) tc_outstream_rotate();
if(AVI_write_frame(avifile, buffer, encode.length, key.is_key_frame)<0) {
tc_log_warn(MOD_NAME, "DivX avi video write error");
return(TC_EXPORT_ERROR);
}
#endif
return(0);
}
if(param->flag == TC_AUDIO) return(tc_audio_encode(param->buffer, param->size, avifile));
// invalid flag
return(TC_EXPORT_ERROR);
}
/* ------------------------------------------------------------
*
* close codec
*
* ------------------------------------------------------------*/
MOD_close
{
vob_t *vob = tc_get_vob();
if(param->flag == TC_AUDIO) return(tc_audio_close());
if(vob->avifile_out!=NULL) {
AVI_close(vob->avifile_out);
vob->avifile_out=NULL;
}
if(param->flag == TC_VIDEO) return(0);
return(TC_EXPORT_ERROR);
}
/* ------------------------------------------------------------
*
* stop encoder
*
* ------------------------------------------------------------*/
MOD_stop
{
if(param->flag == TC_VIDEO) {
#if ENCORE_VERSION >= 20021024
if(divx5_encore(encore_handle, ENC_OPT_RELEASE, NULL, NULL) < 0) {
tc_log_warn(MOD_NAME, "DivX encoder close error");
}
#else
if(divx5_encore(divx->handle, ENC_OPT_RELEASE, NULL, NULL) < 0) {
tc_log_warn(MOD_NAME, "DivX encoder close error");
}
#endif
if(buffer!=NULL) {
free(buffer);
buffer=NULL;
}
//remove codec
dlclose(handle);
#if ENCORE_VERSION >= 20021024
#else
switch(VbrMode) {
case 1:
case 2:
case 3:
VbrControl_close();
break;
default:
break;
}
#endif
return(0);
}
if(param->flag == TC_AUDIO) return(tc_audio_stop());
return(TC_EXPORT_ERROR);
}