diff options
Diffstat (limited to 'debian/transcode/transcode-1.1.7/src/encoder.c')
| -rw-r--r-- | debian/transcode/transcode-1.1.7/src/encoder.c | 1422 |
1 files changed, 1422 insertions, 0 deletions
diff --git a/debian/transcode/transcode-1.1.7/src/encoder.c b/debian/transcode/transcode-1.1.7/src/encoder.c new file mode 100644 index 00000000..d509cd3e --- /dev/null +++ b/debian/transcode/transcode-1.1.7/src/encoder.c @@ -0,0 +1,1422 @@ +/* + * encoder.c -- transcode export layer module, implementation. + * + * Copyright (C) Thomas Oestreich - June 2001 + * Updated and partially rewritten by + * Francesco Romani - January 2006 + * New rotation code written by + * Francesco Romani - May 2006 + * + * 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. + * + */ + +#define SUPPORT_OLD_ENCODER // for now + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "transcode.h" +#include "framebuffer.h" +#include "filter.h" +#include "counter.h" +#include "video_trans.h" +#include "audio_trans.h" +#include "decoder.h" +#include "encoder.h" +#include "frame_threads.h" + +#include "libtc/tcframes.h" + +#include <stdint.h> + +/*************************************************************************/ +/* Our data structure forward declaration */ + +typedef struct tcrotatecontext_ TCRotateContext; +typedef struct tcencoderdata_ TCEncoderData; + +/*************************************************************************/ +/* private function prototypes */ + +/* new-style rotation support */ +static void tc_rotate_init(TCRotateContext *rotor, + const char *video_base_name, + const char *audio_base_name); + +static void tc_rotate_set_frames_limit(TCRotateContext *rotor, + vob_t *vob, uint32_t frames); +static void tc_rotate_set_bytes_limit(TCRotateContext *rotor, + vob_t *vob, uint64_t bytes); + +static void tc_rotate_output_name(TCRotateContext *rotor, vob_t *vob); + +static int tc_rotate_if_needed_null(TCRotateContext *rotor, + vob_t *vob, uint32_t bytes); +static int tc_rotate_if_needed_by_frames(TCRotateContext *rotor, + vob_t *vob, uint32_t bytes); +static int tc_rotate_if_needed_by_bytes(TCRotateContext *rotor, + vob_t *vob, uint32_t bytes); + +/* old-style rotation support is all public, see transcode.h */ + +/* new-style encoder */ + +static int encoder_export(TCEncoderData *data, vob_t *vob); +static void encoder_skip(TCEncoderData *data); +static int encoder_flush(TCEncoderData *data); + +/* rest of API is already public */ + +/* old-style encoder */ +#ifdef SUPPORT_OLD_ENCODER + +static int OLD_tc_export_setup(vob_t *vob, + const char *a_mod, const char *v_mod); +static void OLD_tc_export_shutdown(void); +static int OLD_tc_encoder_init(vob_t *vob); +static int OLD_tc_encoder_open(vob_t *vob); +static int OLD_tc_encoder_close(void); +static int OLD_tc_encoder_stop(void); +static int OLD_encoder_export(TCEncoderData *data, vob_t *vob); + +#endif // SUPPORT_OLD_ENCODER + +/* misc helpers */ +static int need_stop(TCEncoderData *encdata); +static int is_last_frame(TCEncoderData *encdata, int cluster_mode); +static void export_update_formats(vob_t *vob, const TCModuleInfo *vinfo, + const TCModuleInfo *ainfo); +static int alloc_buffers(TCEncoderData *data); +static void free_buffers(TCEncoderData *data); + + +/*************************************************************************/ + +/* + * new encoder module design principles + * 1) keep it simple, stupid + * 2) to have more than one encoder doesn't make sense in transcode, so + * 3) new encoder will be monothread, like the old one + */ + +/*************************************************************************/ +/*************************************************************************/ + +/* + * new-style output rotation support. Always avalaible, but + * only new code is supposed to use it. + * This code is private since only encoder code it's supposed + * to use it. If this change, this code will be put in a + * separate .c/.h pair. + * + * The tricky part of this code it's mainly the + * vob->{video,audio}_out_file mangling. + * This it's still done mainly for legacy reasons. + * After every rotation, such fields will be updated to point + * not to real initialization data, but to private buffers of (a) + * TCRotateContext strucutre. This can hardly seen as good, and + * should be changed/improved in future releases. + * Anyway, original values of mentioned field isn't lost since it + * will be stored in TCRotateContext.{video,audio}_base_name. + * ------------------------------------------------------------ + */ + +/* + * TCExportRotate: + * Generic function called after *every* frame was encoded. + * Rotate output file(s) if condition incapsulate in specific + * functions is satisfied. + * + * Parameters: + * rotor: TCRotateContext to use to check condition. + * vob: pointer to vob_t structure to update with new + * export file(s) name after succesfull rotation. + * bytes: total size of byte encoded (Audio + Video) in last + * rencoding loop. + * Return value: + * TC_OK: successful. + * TC_ERROR: error happened and notified using tc_log*(). + * + * Of course no error can happen if rotating condition isn't met + * (so no rotation it's supposed to happen). + * Please note that caller code CANNOT know when rotation happens: + * This is a feature, not a bug! Having rotation policy incapsulated + * into this code and rotation machinery transparent to caller + * it's EXACTLY the purpose oft this code! :) + */ +typedef int (*TCExportRotate)(TCRotateContext *rotor, vob_t *vob, + uint32_t bytes); + + +struct tcrotatecontext_ { + char video_path_buf[PATH_MAX+1]; + char audio_path_buf[PATH_MAX+1]; + const char *video_base_name; + const char *audio_base_name; + uint32_t chunk_num; + int null_flag; + + uint32_t chunk_frames; + + uint64_t encoded_bytes; + uint64_t chunk_bytes; + + TCExportRotate rotate_if_needed; +}; + +/*************************************************************************/ + + +/* macro goody for output rotation request */ +#define TC_ROTATE_IF_NEEDED(rotor, vob, bytes) \ + ((rotor)->rotate_if_needed((rotor), (vob), bytes)) + +/* + * tc_rotate_init: + * initialize a TCRotateContext with given basenames both for + * audio and video output files. + * Uses null rotation function as default rotation function: + * this means that output rotation just never happen. + * + * Parameters: + * rotor: pointer to a TCRotateContext structure to + * initialize. + * video_base_name: basename for main export file (Audio + Video). + * audio_base_name: basename for auxiliary export file + * (separate audio track). + * Return value: + * None. + */ +static void tc_rotate_init(TCRotateContext *rotor, + const char *video_base_name, + const char *audio_base_name) +{ + if (rotor != NULL) { + memset(rotor, 0, sizeof(TCRotateContext)); + rotor->video_base_name = video_base_name; + rotor->audio_base_name = audio_base_name; + if (video_base_name == NULL || strlen(video_base_name) == 0 + || strcmp(video_base_name, "/dev/null") == 0) { + rotor->null_flag = TC_TRUE; + } else { + rotor->null_flag = TC_FALSE; + strlcpy(rotor->video_path_buf, video_base_name, + sizeof(rotor->video_path_buf)); + /* + * FIXME: Yep, this taste like a duplicate. + * The whole *_out_file thing need a deep review, + * but I want to go a little ahead with the whole + * NMS-powered export layer and write a few more + * NMS export modules before to go with this. -- FR + */ + if (audio_base_name == NULL || strlen(audio_base_name) == 0 + || strcmp(audio_base_name, video_base_name) == 0 + || strcmp(audio_base_name, "/dev/null") == 0) { + /* + * DO NOT separate export audio track, use the same + * export file both for audio and for video + */ + strlcpy(rotor->audio_path_buf, video_base_name, + sizeof(rotor->audio_path_buf)); + } else { + /* separate audio file */ + strlcpy(rotor->audio_path_buf, audio_base_name, + sizeof(rotor->audio_path_buf)); + } + } + rotor->rotate_if_needed = tc_rotate_if_needed_null; + } +} + +/* + * tc_rotate_set {frames,bytes}_limit: + * setup respecitvely frames and bytes limit for each output chunk. + * When calling this function user ask for rotation, so they also + * directly updates vob.{video,audio}_out_file so even first + * tc_encoder_open() later call will uses names of the right format + * (i.e. with the same layout of second and further chunks). + * This is done in order to avoid any later rename() and disomogeneities + * in output file name as experienced in transcode 1.0.x and before. + * + * Calling this functions multiple times will not hurt anything, + * but only the last limit set will be honoured. In other words, + * it's impossible (yet) to limit output BOTH for frames and for size. + * This may change in future releases. + * + * Parameters: + * rotor: TCRotateContext to set limit on. + * vob: pointer to vob structure to update. + * frames: frame limit for each output chunk. + * bytes: size limit for each output chunk. + * Return value: + * None + * Side effects: + * vob parameter will be updated. Modified fields: + * video_out_file, audio_out_file. + */ + +#define PREPARE_OUTPUT_NAME(rotor, vob) \ + if ((rotor)->chunk_num == 0) \ + tc_rotate_output_name((rotor), (vob)) + +static void tc_rotate_set_frames_limit(TCRotateContext *rotor, + vob_t *vob, uint32_t frames) +{ + if (rotor != NULL && !rotor->null_flag) { + rotor->chunk_frames = frames; + rotor->rotate_if_needed = tc_rotate_if_needed_by_frames; + PREPARE_OUTPUT_NAME(rotor, vob); + } +} + +static void tc_rotate_set_bytes_limit(TCRotateContext *rotor, + vob_t *vob, uint64_t bytes) +{ + if (rotor != NULL && !rotor->null_flag) { + rotor->chunk_bytes = bytes; + rotor->rotate_if_needed = tc_rotate_if_needed_by_bytes; + PREPARE_OUTPUT_NAME(rotor, vob); + } +} + +#undef PREPARE_OUTPUT_NAME + +/* helpers ***************************************************************/ + +/* + * all rotation helpers uses at least if()s as possible, so we must + * drop paranoia here + */ + +/* + * tc_rotate_output_name: + * make names of new main/auxiliary output file chunk and updates + * vob fields accordingly. + * + * Parameters: + * rotor: TCRotateContext to use to make new output name(s). + * vob: pointer to vob_t structure to update. + * Return value: + * none. + */ + +/* pretty naif, yet. */ +/* FIXME: OK, we must deeply review the whole *out-file thing ASAP */ +static void tc_rotate_output_name(TCRotateContext *rotor, vob_t *vob) +{ + tc_snprintf(rotor->video_path_buf, sizeof(rotor->video_path_buf), + "%s-%03i", rotor->video_base_name, rotor->chunk_num); + tc_snprintf(rotor->audio_path_buf, sizeof(rotor->audio_path_buf), + "%s-%03i", rotor->audio_base_name, rotor->chunk_num); + vob->video_out_file = rotor->video_path_buf; + vob->audio_out_file = rotor->audio_path_buf; + rotor->chunk_num++; +} + +/*************************************************************************/ +/* + * real rotation policy implementations. Rotate output file(s) + * respectively: + * - never (_null) + * - when encoded frames reach limit (_by_frames) + * - when encoded AND written *bytes* reach limit (_by_bytes). + * + * For details see documentation of TCExportRotate above. + */ + +#define ROTATE_UPDATE_COUNTERS(bytes) do { \ + rotor->encoded_bytes += (bytes); \ +} while (0); + +static int tc_rotate_if_needed_null(TCRotateContext *rotor, + vob_t *vob, uint32_t bytes) +{ + ROTATE_UPDATE_COUNTERS(bytes); + return TC_OK; +} + +#define ROTATE_COMMON_CODE(rotor, vob) do { \ + ret = tc_encoder_close(); \ + if (ret != TC_OK) { \ + tc_log_error(__FILE__, "unable to close output stream"); \ + ret = TC_ERROR; \ + } else { \ + tc_rotate_output_name((rotor), (vob)); \ + tc_log_info(__FILE__, "rotating video output stream to %s", \ + (rotor)->video_path_buf); \ + tc_log_info(__FILE__, "rotating audio output stream to %s", \ + (rotor)->audio_path_buf); \ + ret = tc_encoder_open((vob)); \ + if (ret != TC_OK) { \ + tc_log_error(__FILE__, "unable to reopen output stream"); \ + ret = TC_ERROR; \ + } \ + } \ +} while (0) + + +static int tc_rotate_if_needed_by_frames(TCRotateContext *rotor, + vob_t *vob, uint32_t bytes) +{ + int ret = TC_OK; + ROTATE_UPDATE_COUNTERS(bytes); + + if (tc_get_frames_encoded() >= rotor->chunk_frames) { + ROTATE_COMMON_CODE(rotor, vob); + } + return ret; +} + +static int tc_rotate_if_needed_by_bytes(TCRotateContext *rotor, + vob_t *vob, uint32_t bytes) +{ + int ret = TC_OK; + ROTATE_UPDATE_COUNTERS(bytes); + + if (rotor->encoded_bytes >= rotor->chunk_bytes) { + ROTATE_COMMON_CODE(rotor, vob); + } + return ret; +} + +#undef ROTATE_COMMON_CODE +#undef ROTATE_UPDATE_COUNTERS + +/*************************************************************************/ +/*************************************************************************/ +/* real encoder code */ + + +struct tcencoderdata_ { + /* flags, used internally */ + int error_flag; + int fill_flag; + + /* frame boundaries */ + int frame_first; // XXX + int frame_last; // XXX + /* needed by encoder_skip */ + int saved_frame_last; // XXX + + int this_frame_last; // XXX + int old_frame_last; // XXX + + TCEncoderBuffer *buffer; + + vframe_list_t *venc_ptr; + aframe_list_t *aenc_ptr; + + TCFactory factory; + + TCModule vid_mod; + TCModule aud_mod; + TCModule mplex_mod; + + TCRotateContext rotor_data; + +#ifdef SUPPORT_OLD_ENCODER + transfer_t export_para; + + void *ex_a_handle; + void *ex_v_handle; +#endif +}; + +static TCEncoderData encdata = { + .error_flag = 0, + .fill_flag = 0, + .frame_first = 0, + .frame_last = 0, + .saved_frame_last = 0, + .this_frame_last = 0, + .old_frame_last = 0, + .buffer = NULL, + .venc_ptr = NULL, + .aenc_ptr = NULL, + .factory = NULL, + .vid_mod = NULL, + .aud_mod = NULL, + .mplex_mod = NULL, + /* rotor_data explicitely initialized later */ +#ifdef SUPPORT_OLD_ENCODER + .ex_a_handle = NULL, + .ex_v_handle = NULL, +#endif +}; + + +#define RESET_ATTRIBUTES(ptr) do { \ + (ptr)->attributes = 0; \ +} while (0) + +/* + * is_last_frame: + * check if current frame it's supposed to be the last one in + * encoding frame range. Catch all all known special cases + * + * Parameters: + * encdata: fetch current frame id from this structure reference. + * cluster_mode: boolean flag. When in cluster mode we need to take + * some special care. + * Return value: + * !0: current frame is supposed to be the last one + * 0: otherwise + */ +static int is_last_frame(TCEncoderData *encdata, int cluster_mode) +{ + int fid = encdata->buffer->frame_id; + if (cluster_mode) { + fid -= tc_get_frames_dropped(); + } + + if ((encdata->buffer->vptr->attributes & TC_FRAME_IS_END_OF_STREAM + || encdata->buffer->aptr->attributes & TC_FRAME_IS_END_OF_STREAM)) { + /* `consume' the flag(s) */ + encdata->buffer->vptr->attributes &= ~TC_FRAME_IS_END_OF_STREAM; + encdata->buffer->aptr->attributes &= ~TC_FRAME_IS_END_OF_STREAM; + return 1; + } + return (fid == encdata->frame_last); +} + +/* + * export_update_formats: + * coerce exported formats to the default ones from the loaded + * encoder modules IF AND ONLY IF user doesn't have requested + * specific ones. + * + * That's a temporary workaround until we have a full-NMS + * export layer. + * + * Parameters: + * vob: pointer to vob_t structure to update. + * vinfo: pointer to TCModuleInfo of video encoder module. + * ainfo: pointer to TCModuleInfo of audio encoder module. + * Return value: + * None + */ +static void export_update_formats(vob_t *vob, const TCModuleInfo *vinfo, + const TCModuleInfo *ainfo) +{ + if (vob == NULL || vinfo == NULL || ainfo == NULL) { + /* should never happen */ + tc_log_error(__FILE__, "missing export formats references"); + } + /* + * OK, that's pretty hackish since export_attributes should + * go away in near future. Neverthless, ex_a_codec features + * a pretty unuseful default (CODEC_MP3), so we don't use + * such default value to safely distinguish between -N given + * or not given. + * And so we must use another flag, and export_attributes are + * the simplest things that work, now/ + */ + if (!(vob->export_attributes & TC_EXPORT_ATTRIBUTE_VCODEC)) { + vob->ex_v_codec = vinfo->codecs_out[0]; + } + if (!(vob->export_attributes & TC_EXPORT_ATTRIBUTE_ACODEC)) { + vob->ex_a_codec = ainfo->codecs_out[0]; + } +} + +/* ------------------------------------------------------------ + * + * export init + * + * ------------------------------------------------------------*/ + +int tc_export_init(TCEncoderBuffer *buffer, TCFactory factory) +{ + if (buffer == NULL) { + tc_log_error(__FILE__, "missing encoder buffer reference"); + return TC_ERROR; + } + encdata.buffer = buffer; + +#ifndef SUPPORT_OLD_ENCODER // factory==NULL to signal use of old code + if (factory == NULL) { + tc_log_error(__FILE__, "missing factory reference"); + return TC_ERROR; + } +#endif + encdata.factory = factory; + return TC_OK; +} + +int tc_export_setup(vob_t *vob, + const char *a_mod, const char *v_mod, const char *m_mod) +{ + int match = 0; + const char *mod_name = NULL; + +#ifdef SUPPORT_OLD_ENCODER + if (!encdata.factory) + return OLD_tc_export_setup(vob, a_mod, v_mod); +#endif + + if (verbose >= TC_DEBUG) { + tc_log_info(__FILE__, "loading export modules"); + } + + mod_name = (a_mod == NULL) ?TC_DEFAULT_EXPORT_AUDIO :a_mod; + encdata.aud_mod = tc_new_module(encdata.factory, "encode", mod_name, TC_AUDIO); + if (!encdata.aud_mod) { + tc_log_error(__FILE__, "can't load audio encoder"); + return TC_ERROR; + } + mod_name = (v_mod == NULL) ?TC_DEFAULT_EXPORT_VIDEO :v_mod; + encdata.vid_mod = tc_new_module(encdata.factory, "encode", mod_name, TC_VIDEO); + if (!encdata.vid_mod) { + tc_log_error(__FILE__, "can't load video encoder"); + return TC_ERROR; + } + mod_name = (m_mod == NULL) ?TC_DEFAULT_EXPORT_MPLEX :m_mod; + encdata.mplex_mod = tc_new_module(encdata.factory, "multiplex", mod_name, + TC_VIDEO|TC_AUDIO); + if (!encdata.mplex_mod) { + tc_log_error(__FILE__, "can't load multiplexor"); + return TC_ERROR; + } + export_update_formats(vob, tc_module_get_info(encdata.vid_mod), + tc_module_get_info(encdata.aud_mod)); + + match = tc_module_match(vob->ex_a_codec, + encdata.aud_mod, encdata.mplex_mod); + if (!match) { + tc_log_error(__FILE__, "audio encoder incompatible " + "with multiplexor"); + return TC_ERROR; + } + match = tc_module_match(vob->ex_v_codec, + encdata.vid_mod, encdata.mplex_mod); + if (!match) { + tc_log_error(__FILE__, "video encoder incompatible " + "with multiplexor"); + return TC_ERROR; + } + tc_rotate_init(&encdata.rotor_data, + vob->video_out_file, vob->audio_out_file); + + return TC_OK; +} + +/* ------------------------------------------------------------ + * + * export close, unload modules + * + * ------------------------------------------------------------*/ + +void tc_export_shutdown(void) +{ +#ifdef SUPPORT_OLD_ENCODER + if (!encdata.factory) + return OLD_tc_export_shutdown(); +#endif + + if (verbose >= TC_DEBUG) { + tc_log_info(__FILE__, "unloading export modules"); + } + + tc_del_module(encdata.factory, encdata.mplex_mod); + tc_del_module(encdata.factory, encdata.vid_mod); + tc_del_module(encdata.factory, encdata.aud_mod); +} + + +/* ------------------------------------------------------------ + * + * encoder init + * + * ------------------------------------------------------------*/ + +int tc_encoder_init(vob_t *vob) +{ + int ret; + const char *options = NULL; + +#ifdef SUPPORT_OLD_ENCODER + if (!encdata.factory) + return OLD_tc_encoder_init(vob); +#endif + + ret = alloc_buffers(&encdata); + if (ret != TC_OK) { + tc_log_error(__FILE__, "can't allocate encoder buffers"); + return TC_ERROR; + } + + options = (vob->ex_v_string) ?vob->ex_v_string :""; + ret = tc_module_configure(encdata.vid_mod, options, vob); + if (ret != TC_OK) { + tc_log_warn(__FILE__, "video export module error: init failed"); + return TC_ERROR; + } + + options = (vob->ex_a_string) ?vob->ex_a_string :""; + ret = tc_module_configure(encdata.aud_mod, options, vob); + if (ret != TC_OK) { + tc_log_warn(__FILE__, "audio export module error: init failed"); + return TC_ERROR; + } + + return TC_OK; +} + + +/* ------------------------------------------------------------ + * + * encoder open + * + * ------------------------------------------------------------*/ + +int tc_encoder_open(vob_t *vob) +{ + int ret; + const char *options = NULL; + +#ifdef SUPPORT_OLD_ENCODER + if (!encdata.factory) + return OLD_tc_encoder_open(vob); +#endif + + options = vob->ex_m_string ? vob->ex_m_string : ""; + ret = tc_module_configure(encdata.mplex_mod, options, vob); + if (ret == TC_ERROR) { + tc_log_warn(__FILE__, "multiplexor module error: init failed"); + return TC_ERROR; + } + + // XXX + tc_module_pass_extradata(encdata.vid_mod, encdata.mplex_mod); + + return TC_OK; +} + + +/* ------------------------------------------------------------ + * + * encoder close + * + * ------------------------------------------------------------*/ + +int tc_encoder_close(void) +{ + int ret; + +#ifdef SUPPORT_OLD_ENCODER + if (!encdata.factory) + return OLD_tc_encoder_close(); +#endif + + /* old style code handle flushing in modules, not here */ + ret = encoder_flush(&encdata); + if (ret != TC_OK) { + tc_log_warn(__FILE__, "error while closing encoder: flush failed"); + return TC_ERROR; + } + + ret = tc_module_stop(encdata.mplex_mod); + if (ret != TC_OK) { + tc_log_warn(__FILE__, "multiplexor module error: stop failed"); + return TC_ERROR; + } + + if(verbose >= TC_DEBUG) { + tc_log_info(__FILE__, "encoder closed"); + } + return TC_OK; +} + + +/* ------------------------------------------------------------ + * + * encoder stop + * + * ------------------------------------------------------------*/ + +int tc_encoder_stop(void) +{ + int ret; + +#ifdef SUPPORT_OLD_ENCODER + if (!encdata.factory) + return OLD_tc_encoder_stop(); +#endif + + ret = tc_module_stop(encdata.vid_mod); + if (ret != TC_OK) { + tc_log_warn(__FILE__, "video export module error: stop failed"); + return TC_ERROR; + } + + ret = tc_module_stop(encdata.aud_mod); + if (ret != TC_OK) { + tc_log_warn(__FILE__, "audio export module error: stop failed"); + return TC_ERROR; + } + + free_buffers(&encdata); + + if(verbose >= TC_DEBUG) { + tc_log_info(__FILE__, "encoder stopped"); + } + return TC_OK; +} + +/* ------------------------------------------------------------ + * + * encoder main loop helpers + * + * ------------------------------------------------------------*/ + +static int alloc_buffers(TCEncoderData *data) +{ + data->venc_ptr = vframe_alloc_single(); + if (data->venc_ptr == NULL) { + goto no_vframe; + } + data->aenc_ptr = aframe_alloc_single(); + if (data->aenc_ptr == NULL) { + goto no_aframe; + } + return TC_OK; + +no_aframe: + tc_del_video_frame(data->venc_ptr); +no_vframe: + return TC_ERROR; +} + +static void free_buffers(TCEncoderData *data) +{ + tc_del_video_frame(data->venc_ptr); + tc_del_audio_frame(data->aenc_ptr); +} + +/* + * NOTE about counter/condition/mutex handling inside various + * encoder helpers. + * + * Code are still a little bit confusing since things aren't + * updated or used at the same function level. + * Code works, but isn't still well readable. + * We need stil more cleanup and refactoring for future releases. + */ + + +/* + * dispatch the acquired frames to encoder modules, and adjust frame counters + */ +static int encoder_export(TCEncoderData *data, vob_t *vob) +{ + int video_delayed = 0; + int ret; + +#ifdef SUPPORT_OLD_ENCODER + if (!encdata.factory) + return OLD_encoder_export(data, vob); +#endif + /* remove spurious attributes */ + RESET_ATTRIBUTES(data->venc_ptr); + RESET_ATTRIBUTES(data->aenc_ptr); + + /* step 1: encode video */ + ret = tc_module_encode_video(data->vid_mod, + data->buffer->vptr, data->venc_ptr); + if (ret != TC_OK) { + tc_log_error(__FILE__, "error encoding video frame"); + data->error_flag = 1; + } + if (data->venc_ptr->attributes & TC_FRAME_IS_DELAYED) { + data->venc_ptr->attributes &= ~TC_FRAME_IS_DELAYED; + video_delayed = 1; + } + + /* step 2: encode audio */ + if (video_delayed) { + data->buffer->aptr->attributes |= TC_FRAME_IS_CLONED; + tc_log_info(__FILE__, "Delaying audio"); + } else { + ret = tc_module_encode_audio(data->aud_mod, + data->buffer->aptr, data->aenc_ptr); + if (ret != TC_OK) { + tc_log_error(__FILE__, "error encoding audio frame"); + data->error_flag = 1; + } + } + + /* step 3: multiplex and rotate */ + // FIXME: Do we really need bytes-written returned from this, or can + // we just return TC_OK/TC_ERROR like other functions? --AC + ret = tc_module_multiplex(data->mplex_mod, + data->venc_ptr, data->aenc_ptr); + if (ret < 0) { + tc_log_error(__FILE__, "error multiplexing encoded frames"); + data->error_flag = 1; + } + data->error_flag = TC_ROTATE_IF_NEEDED(&encdata.rotor_data, vob, ret); + + /* step 4: show and update stats */ + if (tc_progress_meter) { + int last = (data->frame_last == TC_FRAME_LAST) + ?(-1) :data->frame_last; + if (!data->fill_flag) { + data->fill_flag = 1; + } + counter_print(1, data->buffer->frame_id, data->frame_first, last); + } + + tc_update_frames_encoded(1); + return (data->error_flag) ?TC_ERROR :TC_OK; +} + + +#define RETURN_IF_NOT_OK(RET, KIND) do { \ + if ((RET) != TC_OK) { \ + tc_log_error(__FILE__, "error encoding final %s frame", (KIND)); \ + return TC_ERROR; \ + } \ +} while (0) + +#define RETURN_IF_MUX_ERROR(BYTES) do { \ + if ((BYTES) < 0) { \ + tc_log_error(__FILE__, "error multiplexing final audio frame"); \ + return TC_ERROR; \ + } \ +} while (0) + + +/* DO NOT rotate here, this data belongs to current chunk */ +static int encoder_flush(TCEncoderData *data) +{ + int ret = TC_ERROR, bytes = 0; + + do { + RESET_ATTRIBUTES(data->aenc_ptr); + ret = tc_module_encode_audio(data->aud_mod, NULL, data->aenc_ptr); + RETURN_IF_NOT_OK(ret, "audio"); + + bytes = tc_module_multiplex(data->mplex_mod, NULL, data->aenc_ptr); + } while (bytes != 0); + RETURN_IF_MUX_ERROR(bytes); + + do { + RESET_ATTRIBUTES(data->venc_ptr); + ret = tc_module_encode_video(data->vid_mod, NULL, data->venc_ptr); + RETURN_IF_NOT_OK(ret, "video"); + + bytes = tc_module_multiplex(data->mplex_mod, data->venc_ptr, NULL); + } while (bytes != 0); + RETURN_IF_MUX_ERROR(bytes); + + return TC_OK; +} + + +void tc_export_rotation_limit_frames(vob_t *vob, uint32_t frames) +{ +#ifdef SUPPORT_OLD_ENCODER + if (!encdata.factory) + return; +#endif + + tc_rotate_set_frames_limit(&encdata.rotor_data, vob, frames); +} + +void tc_export_rotation_limit_megabytes(vob_t *vob, uint32_t megabytes) +{ +#ifdef SUPPORT_OLD_ENCODER + if (!encdata.factory) + return; +#endif + + tc_rotate_set_bytes_limit(&encdata.rotor_data, + vob, megabytes * 1024 * 1024); +} + +/*************************************************************************/ + +#ifdef SUPPORT_OLD_ENCODER + +#include "dl_loader.h" + +/* ------------------------------------------------------------ + * + * export init + * + * ------------------------------------------------------------*/ + +static int OLD_tc_export_setup(vob_t *vob, + const char *a_mod, const char *v_mod) +{ + const char *mod_name = NULL; + + // load export modules + mod_name = (a_mod == NULL) ?TC_DEFAULT_EXPORT_AUDIO :a_mod; + encdata.ex_a_handle = load_module((char*)mod_name, TC_EXPORT + TC_AUDIO); + if (encdata.ex_a_handle == NULL) { + tc_log_warn(__FILE__, "loading audio export module failed"); + return TC_ERROR; + } + + mod_name = (v_mod==NULL) ?TC_DEFAULT_EXPORT_VIDEO :v_mod; + encdata.ex_v_handle = load_module((char*)mod_name, TC_EXPORT + TC_VIDEO); + if (encdata.ex_v_handle == NULL) { + tc_log_warn(__FILE__, "loading video export module failed"); + return TC_ERROR; + } + + encdata.export_para.flag = verbose; + tca_export(TC_EXPORT_NAME, &encdata.export_para, NULL); + + if(encdata.export_para.flag != verbose) { + // module returned capability flag + int cc=0; + + if (verbose & TC_DEBUG) { + tc_log_info(__FILE__, "audio capability flag 0x%x | 0x%x", + encdata.export_para.flag, vob->im_a_codec); + } + + switch (vob->im_a_codec) { + case CODEC_PCM: + cc = (encdata.export_para.flag & TC_CAP_PCM); + break; + case CODEC_AC3: + cc = (encdata.export_para.flag & TC_CAP_AC3); + break; + case CODEC_RAW: + cc = (encdata.export_para.flag & TC_CAP_AUD); + break; + default: + cc = 0; + } + + if (cc == 0) { + tc_log_warn(__FILE__, "audio codec not supported by export module"); + return TC_ERROR; + } + } else { /* encdata.export_para.flag == verbose */ + if (vob->im_a_codec != CODEC_PCM) { + tc_log_warn(__FILE__, "audio codec not supported by export module"); + return TC_ERROR; + } + } + + encdata.export_para.flag = verbose; + tcv_export(TC_EXPORT_NAME, &encdata.export_para, NULL); + + if (encdata.export_para.flag != verbose) { + // module returned capability flag + int cc = 0; + + if (verbose & TC_DEBUG) { + tc_log_info(__FILE__, "video capability flag 0x%x | 0x%x", + encdata.export_para.flag, vob->im_v_codec); + } + + switch (vob->im_v_codec) { + case CODEC_RGB: + cc = (encdata.export_para.flag & TC_CAP_RGB); + break; + case CODEC_YUV: + cc = (encdata.export_para.flag & TC_CAP_YUV); + break; + case CODEC_YUV422: + cc = (encdata.export_para.flag & TC_CAP_YUV422); + break; + case CODEC_RAW: + case CODEC_RAW_YUV: /* fallthrough */ + cc = (encdata.export_para.flag & TC_CAP_VID); + break; + default: + cc = 0; + } + + if (cc == 0) { + tc_log_warn(__FILE__, "video codec not supported by export module"); + return TC_ERROR; + } + } else { /* encdata.export_para.flag == verbose */ + if (vob->im_v_codec != CODEC_RGB) { + tc_log_warn(__FILE__, "video codec not supported by export module"); + return TC_ERROR; + } + } + + return TC_OK; +} + +/* ------------------------------------------------------------ + * + * export close, unload modules + * + * ------------------------------------------------------------*/ + +static void OLD_tc_export_shutdown(void) +{ + if (verbose & TC_DEBUG) { + tc_log_info(__FILE__, "unloading export modules"); + } + + unload_module(encdata.ex_a_handle); + unload_module(encdata.ex_v_handle); +} + + +/* ------------------------------------------------------------ + * + * encoder init + * + * ------------------------------------------------------------*/ + +static int OLD_tc_encoder_init(vob_t *vob) +{ + int ret; + + encdata.export_para.flag = TC_VIDEO; + ret = tcv_export(TC_EXPORT_INIT, &encdata.export_para, vob); + if (ret != TC_OK) { + tc_log_warn(__FILE__, "video export module error: init failed"); + return TC_ERROR; + } + + encdata.export_para.flag = TC_AUDIO; + ret = tca_export(TC_EXPORT_INIT, &encdata.export_para, vob); + if (ret != TC_OK) { + tc_log_warn(__FILE__, "audio export module error: init failed"); + return TC_ERROR; + } + + return TC_OK; +} + + +/* ------------------------------------------------------------ + * + * encoder open + * + * ------------------------------------------------------------*/ + +static int OLD_tc_encoder_open(vob_t *vob) +{ + int ret; + + encdata.export_para.flag = TC_VIDEO; + ret = tcv_export(TC_EXPORT_OPEN, &encdata.export_para, vob); + if (ret != TC_OK) { + tc_log_warn(__FILE__, "video export module error: open failed"); + return TC_ERROR; + } + + encdata.export_para.flag = TC_AUDIO; + ret = tca_export(TC_EXPORT_OPEN, &encdata.export_para, vob); + if (ret != TC_OK) { + tc_log_warn(__FILE__, "audio export module error: open failed"); + return TC_ERROR; + } + + return TC_OK; +} + + +/* ------------------------------------------------------------ + * + * encoder close + * + * ------------------------------------------------------------*/ + +static int OLD_tc_encoder_close(void) +{ + /* + * close, errors not fatal. + * flushing handled internally by export modules. + */ + + encdata.export_para.flag = TC_AUDIO; + tca_export(TC_EXPORT_CLOSE, &encdata.export_para, NULL); + + encdata.export_para.flag = TC_VIDEO; + tcv_export(TC_EXPORT_CLOSE, &encdata.export_para, NULL); + + if(verbose & TC_DEBUG) { + tc_log_info(__FILE__, "encoder closed"); + } + return TC_OK; +} + + +/* ------------------------------------------------------------ + * + * encoder stop + * + * ------------------------------------------------------------*/ + +static int OLD_tc_encoder_stop(void) +{ + int ret; + + encdata.export_para.flag = TC_VIDEO; + ret = tcv_export(TC_EXPORT_STOP, &encdata.export_para, NULL); + if (ret != TC_OK) { + tc_log_warn(__FILE__, "video export module error: stop failed"); + return TC_ERROR; + } + + encdata.export_para.flag = TC_AUDIO; + ret = tca_export(TC_EXPORT_STOP, &encdata.export_para, NULL); + if (ret != TC_OK) { + tc_log_warn(__FILE__, "audio export module error: stop failed"); + return TC_ERROR; + } + + return TC_OK; +} + +/* + * dispatch the acquired frames to encoder modules + */ +static int OLD_encoder_export(TCEncoderData *data, vob_t *vob) +{ + int video_delayed = 0; + + /* encode and export video frame */ + data->export_para.buffer = data->buffer->vptr->video_buf; + data->export_para.size = data->buffer->vptr->video_size; + data->export_para.attributes = data->buffer->vptr->attributes; + if (data->buffer->vptr->attributes & TC_FRAME_IS_KEYFRAME) { + data->export_para.attributes |= TC_FRAME_IS_KEYFRAME; + } + data->export_para.flag = TC_VIDEO; + + if(tcv_export(TC_EXPORT_ENCODE, &data->export_para, vob) < 0) { + tc_log_warn(__FILE__, "error encoding video frame"); + data->error_flag = 1; + } + /* maybe clone? */ + data->buffer->vptr->attributes = data->export_para.attributes; + if (data->buffer->vptr->attributes & TC_FRAME_IS_DELAYED) { + data->buffer->vptr->attributes &= ~TC_FRAME_IS_DELAYED; + video_delayed = 1; + } + + /* encode and export audio frame */ + data->export_para.buffer = data->buffer->aptr->audio_buf; + data->export_para.size = data->buffer->aptr->audio_size; + data->export_para.attributes = data->buffer->aptr->attributes; + data->export_para.flag = TC_AUDIO; + + if(video_delayed > 0) { + data->buffer->aptr->attributes |= TC_FRAME_IS_CLONED; + tc_log_info(__FILE__, "Delaying audio"); + } else { + if (tca_export(TC_EXPORT_ENCODE, &data->export_para, vob) < 0) { + tc_log_warn(__FILE__, "error encoding audio frame"); + data->error_flag = 1; + } + + /* maybe clone? */ + data->buffer->aptr->attributes = data->export_para.attributes; + } + + if (tc_progress_meter) { + int last = (data->frame_last == TC_FRAME_LAST) ?(-1) :data->frame_last; + if (!data->fill_flag) { + data->fill_flag = 1; + } + counter_print(1, data->buffer->frame_id, data->frame_first, last); + } + + // XXX: _always_ update? + tc_update_frames_encoded(1); + return data->error_flag ? TC_ERROR : TC_OK; +} + + +/************************************************************************* + * old style rotation support code. + * TEMPORARY merged here until it will be deleted together with + * old encoder code when NMS-powered export layer is ready to switch + * (read: when we have enough encode/multiplexor module to go). + *************************************************************************/ + +#include "libtc/xio.h" + +//----------------------------------------------------------------- +// +// r,R - switch output file +// +//----------------------------------------------------------------- + +static int rotate_ctr = 0; +static int rotate_flag = 0; +static char *base = NULL; + +void tc_outstream_rotate_request(void) +{ + //set flag + rotate_flag = 1; +} + +void tc_outstream_rotate(void) +{ + char buf[TC_BUF_MAX]; + vob_t *vob=tc_get_vob(); + + if (!rotate_flag) + return; + + //reset flag to avoid re-entry + rotate_flag=0; + + // do not try to rename /dev/null + if(strcmp(vob->video_out_file, "/dev/null") == 0) + return; + + // get current outfile basename + base = tc_strdup(vob->video_out_file); + + //check + if (base == NULL) + return; + + // close output + if (tc_encoder_close()<0) + tc_error("failed to close output"); + + // create new filename + tc_snprintf(buf, sizeof(buf), "%s-%03d", base, rotate_ctr++); + //rename old outputfile + if (xio_rename(base, buf) < 0) + tc_error("failed to rename output file"); + + // reopen output + if (tc_encoder_open(vob) < 0) + tc_error("failed to open output"); + + tc_log_info(__FILE__, "outfile %s saved to %s", base, buf); + free(base); +} + +/*************************************************************************/ + +#endif // SUPPORT_OLD_ENCODER + +/* + * fake encoding, simply adjust frame counters. + */ +static void encoder_skip(TCEncoderData *data) +{ + if (tc_progress_meter) { + if (!data->fill_flag) { + data->fill_flag = 1; + } + counter_print(0, data->buffer->frame_id, data->saved_frame_last, + data->frame_first-1); + } + data->buffer->vptr->attributes |= TC_FRAME_IS_OUT_OF_RANGE; + data->buffer->aptr->attributes |= TC_FRAME_IS_OUT_OF_RANGE; +} + +static int need_stop(TCEncoderData *encdata) +{ + return !tc_running() || !tc_import_status() || encdata->error_flag; +} + +/* ------------------------------------------------------------ + * + * encoder main loop + * + * ------------------------------------------------------------*/ + +void tc_encoder_loop(vob_t *vob, int frame_first, int frame_last) +{ + int err = 0, eos = 0; /* End Of Stream flag */ + + if (encdata.this_frame_last != frame_last) { + encdata.old_frame_last = encdata.this_frame_last; + encdata.this_frame_last = frame_last; + } + + encdata.error_flag = 0; /* reset */ + encdata.frame_first = frame_first; + encdata.frame_last = frame_last; + encdata.saved_frame_last = encdata.old_frame_last; + + while (!need_stop(&encdata)) { + /* stop here if pause requested */ + tc_pause(); + + err = encdata.buffer->acquire_video_frame(encdata.buffer, vob); + if (err) { + if (verbose >= TC_DEBUG) { + tc_log_warn(__FILE__, "failed to acquire next raw video frame"); + } + break; /* can't acquire video frame */ + } + + err = encdata.buffer->acquire_audio_frame(encdata.buffer, vob); + if (err) { + if (verbose >= TC_DEBUG) { + tc_log_warn(__FILE__, "failed to acquire next raw audio frame"); + } + break; /* can't acquire frame */ + } + + eos = is_last_frame(&encdata, tc_cluster_mode); + if (eos) { + break; + } + + /* check frame id */ + if (frame_first <= encdata.buffer->frame_id + && encdata.buffer->frame_id < frame_last) { + encoder_export(&encdata, vob); + } else { /* frame not in range */ + encoder_skip(&encdata); + } /* frame processing loop */ + + /* release frame buffer memory */ + encdata.buffer->dispose_video_frame(encdata.buffer); + encdata.buffer->dispose_audio_frame(encdata.buffer); + } + /* main frame decoding loop */ + + if (verbose >= TC_CLEANUP) { + if (eos) { + tc_log_info(__FILE__, "encoder last frame finished (%i/%i)", + encdata.buffer->frame_id, encdata.frame_last); + } + tc_log_info(__FILE__, "export terminated - buffer(s) empty"); + } + return; +} + + +/*************************************************************************/ + +/* + * Local variables: + * c-file-style: "stroustrup" + * c-file-offsets: ((case-label . *) (statement-case-intro . *)) + * indent-tabs-mode: nil + * End: + * + * vim: expandtab shiftwidth=4: + */ |
