/* * import_framegen.c -- generate an infinite stream of synthetic frames * for testing purposes. * (C) 2009-2010 Francesco Romani * using some video frame filling code which is * Copyright (C) Thomas Oestreich - June 2001 * some code derived by alsa-utils/speakertest, which is * Copyright (C) 2005 Nathan Hurst * * 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 of the License, 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 this program. If not, see . */ /*%* *%* DESCRIPTION *%* This module produces an infinite stream of raw synthetic frames. *%* It is intended for testing purposes. *%* *%* #BUILD-DEPENDS *%* *%* #DEPENDS *%* *%* PROCESSING *%* import/demuxer *%* *%* MEDIA *%* video, audio *%* *%* #INPUT *%* *%* OUTPUT *%* YUV420P, PCM *%* *%* #OPTION *%*/ /* * TODO (unordered): * - add more generators (at least a video/RGB one) * - review internal generator API * -- to emit cooked frames instead of raw bytes it is better? * -- need to explicitely separate A/V generators? * -- need to distinguish the function to call for A/V? * - accepts (and use!) random seed as module options */ #include "src/transcode.h" #include "aclib/imgconvert.h" #include "libtc/optstr.h" #include "libtc/tcmodule-plugin.h" #include "pink.h" #include "config.h" #define LEGACY 1 #ifdef LEGACY # define MOD_NAME "import_framegen.so" #else # define MOD_NAME "demultiplex_framegen.so" #endif #define MOD_VERSION "v0.1.0 (2009-06-21)" #define MOD_CAP "generate stream of testframes" #define MOD_AUTHOR "Francesco Romani" #define MOD_FEATURES \ TC_MODULE_FEATURE_DEMULTIPLEX|TC_MODULE_FEATURE_AUDIO|TC_MODULE_FEATURE_VIDEO #define MOD_FLAGS \ TC_MODULE_FLAG_RECONFIGURABLE /*************************************************************************/ typedef struct tcframegensource_ TCFrameGenSource; struct tcframegensource_ { void *priv; const char *name; const char *media; int32_t seed; int (*get_data)(TCFrameGenSource *handle, uint8_t *data, int maxdata, int *datalen); int (*close)(TCFrameGenSource *handle); }; static int tc_framegen_source_get_data(TCFrameGenSource *handle, uint8_t *data, int maxdata, int *datalen) { return handle->get_data(handle, data, maxdata, datalen); } static int tc_framegen_source_close(TCFrameGenSource *handle) { return handle->close(handle); } static int framegen_generic_close(TCFrameGenSource *handle) { tc_free(handle); return TC_OK; } /*************************************************************************/ typedef struct pinknoisedata_ PinkNoiseData; struct pinknoisedata_ { pink_noise_t pink; }; /* only signed 16 bits LE samples supported so far */ static int framegen_pink_noise_get_data(TCFrameGenSource *handle, uint8_t *data, int maxdata, int *datalen) { PinkNoiseData *PN = handle->priv; int16_t *samples = (int16_t*)data; int i; for (i = 0; i < maxdata; i++) { int32_t res = generate_pink_noise_sample(&(PN->pink)) * 0x03fffffff; /* Don't use MAX volume */ samples[i] = res >> 16; } return TC_OK; } static int framegen_pink_noise_init(PinkNoiseData *PN, vob_t *vob) { int ret = TC_ERROR; if (vob->a_bits == 16) { initialize_pink_noise(&(PN->pink), 16); /* this doesn't depend to the sample size */ ret = TC_OK; } return ret; } static TCFrameGenSource *tc_framegen_source_open_audio_pink_noise(vob_t *vob, int32_t seed) { uint8_t *p = tc_zalloc(sizeof(TCFrameGenSource) + sizeof(PinkNoiseData)); TCFrameGenSource *FG = (TCFrameGenSource*)p; PinkNoiseData *PN = (PinkNoiseData*)(p + sizeof(TCFrameGenSource)); if (FG) { int ret = framegen_pink_noise_init(PN, vob); if (ret != TC_OK) { tc_free(p); FG = NULL; PN = NULL; } else { FG->priv = PN; FG->name = "pink noise generator"; FG->media = "audio"; FG->get_data = framegen_pink_noise_get_data; FG->close = framegen_generic_close; } } return FG; } /*************************************************************************/ typedef struct colorwavedata_ ColorWaveData; struct colorwavedata_ { int width; int height; int index; }; static int framegen_color_wave_get_data(TCFrameGenSource *handle, uint8_t *data, int maxdata, int *datalen) { ColorWaveData *CW = handle->priv; uint8_t *planes[3] = { NULL, NULL, NULL }; int size = CW->width * CW->height * 3 / 2; int x, y; if (maxdata < size) { return TC_ERROR; } YUV_INIT_PLANES(planes, data, IMG_YUV_DEFAULT, CW->width, CW->height); memset(data, 0x80, size); /* FIXME */ for (y = 0; y < CW->height; y++) { for (x = 0; x < CW->width; x++) { planes[0][y * CW->width + x] = x + y + CW->index * 3; } } for (y = 0; y < CW->height/2; y++) { for (x = 0; x < CW->width/2; x++) { planes[1][y * CW->width/2 + x] = 128 + y + CW->index * 2; planes[2][y * CW->width/2 + x] = 64 + x + CW->index * 5; } } CW->index++; *datalen = size; return TC_OK; } static int framegen_color_wave_init(ColorWaveData *CW, vob_t *vob) { int ret = TC_ERROR; if (vob->im_v_codec == TC_CODEC_YUV420P || vob->im_v_codec == CODEC_YUV) { CW->index = 0; CW->width = vob->im_v_width; CW->height = vob->im_v_height; ret = TC_OK; } return ret; } static TCFrameGenSource *tc_framegen_source_open_video_color_wave(vob_t *vob, int32_t seed) { uint8_t *p = tc_zalloc(sizeof(TCFrameGenSource) + sizeof(ColorWaveData)); TCFrameGenSource *FG = (TCFrameGenSource*)p; ColorWaveData *CW = (ColorWaveData*)(p + sizeof(TCFrameGenSource)); if (FG) { int ret = framegen_color_wave_init(CW, vob); if (ret != TC_OK) { tc_free(p); FG = NULL; CW = NULL; } else { FG->priv = CW; FG->name = "color wave generator"; FG->media = "video"; FG->get_data = framegen_color_wave_get_data; FG->close = framegen_generic_close; } } return FG; } /*************************************************************************/ #define RETURN_IF_FAILED(RET, MSG) do { \ if ((RET) < 0) { \ tc_log_error(MOD_NAME, "%s", (MSG)); \ return TC_ERROR; \ } \ } while (0) #define RETURN_IF_ERROR(RET, MSG) do { \ if ((RET) != TC_OK) { \ tc_log_error(MOD_NAME, "%s", (MSG)); \ return RET; \ } \ } while (0) #define RETURN_IF_NULL(PTR, MSG) do { \ if (!(PTR)) { \ tc_log_error(MOD_NAME, "%s", (MSG)); \ return TC_ERROR; \ } \ } while (0) /*************************************************************************/ /* New-Style module interface */ static const char tc_framegen_help[] = "" "Overview:\n" " This module reads audio samples from an ALSA device using libalsa.\n" "Options:\n" " device=dev selects ALSA device to use\n" " help produce module overview and options explanations\n"; typedef struct tcframegenprivatedata_ TCFrameGenPrivateData; struct tcframegenprivatedata_ { TCFrameGenSource *video_gen; TCFrameGenSource *audio_gen; }; TC_MODULE_GENERIC_INIT(tc_framegen, TCFrameGenPrivateData) TC_MODULE_GENERIC_FINI(tc_framegen) static int tc_framegen_configure(TCModuleInstance *self, const char *options, vob_t *vob) { TCFrameGenPrivateData *priv = NULL; TC_MODULE_SELF_CHECK(self, "configure"); priv = self->userdata; /* FIXME: add proper option handling/mode selection */ priv->video_gen = tc_framegen_source_open_video_color_wave(vob, 0); RETURN_IF_NULL((priv->video_gen), "configure: failed to open the video frame generator"); priv->audio_gen = tc_framegen_source_open_audio_pink_noise(vob, 0); RETURN_IF_NULL((priv->audio_gen), "configure: failed to open the audio frame generator"); return TC_OK; } static int tc_framegen_inspect(TCModuleInstance *self, const char *param, const char **value) { TC_MODULE_SELF_CHECK(self, "inspect"); if (optstr_lookup(param, "help")) { *value = tc_framegen_help; } return TC_OK; } static int tc_framegen_stop(TCModuleInstance *self) { TCFrameGenPrivateData *priv = NULL; int ret = 0; TC_MODULE_SELF_CHECK(self, "stop"); priv = self->userdata; ret = tc_framegen_source_close(priv->video_gen); RETURN_IF_ERROR(ret, "stop: failed to close the video frame generator"); ret = tc_framegen_source_close(priv->audio_gen); RETURN_IF_ERROR(ret, "stop: failed to close the audio frame generator"); return TC_OK; } static int tc_framegen_demultiplex(TCModuleInstance *self, TCFrameVideo *vframe, TCFrameAudio *aframe) { int vret = 0, aret = 0; TCFrameGenPrivateData *priv = NULL; TC_MODULE_SELF_CHECK(self, "demultiplex"); priv = self->userdata; if (vframe != NULL) { vret = tc_framegen_source_get_data(priv->video_gen, vframe->video_buf, vframe->video_size, &vframe->video_len); RETURN_IF_FAILED(vret, "demux: failed to pull a new video frame"); } if (aframe != NULL) { aret = tc_framegen_source_get_data(priv->audio_gen, aframe->audio_buf, aframe->audio_size, &aframe->audio_len); RETURN_IF_FAILED(aret, "demux: failed to pull a new audio frame"); } return (vret + aret); } /*************************************************************************/ static const TCCodecID tc_framegen_codecs_in[] = { TC_CODEC_ERROR }; /* a demultiplexor is at the begin of the pipeline */ static const TCCodecID tc_framegen_codecs_out[] = { TC_CODEC_YUV420P, TC_CODEC_PCM, TC_CODEC_ERROR, }; static const TCFormatID tc_framegen_formats_in[] = { TC_FORMAT_ERROR, }; static const TCFormatID tc_framegen_formats_out[] = { TC_FORMAT_ERROR }; static const TCModuleInfo tc_framegen_info = { .features = MOD_FEATURES, .flags = MOD_FLAGS, .name = MOD_NAME, .version = MOD_VERSION, .description = MOD_CAP, .codecs_in = tc_framegen_codecs_in, .codecs_out = tc_framegen_codecs_out, .formats_in = tc_framegen_formats_in, .formats_out = tc_framegen_formats_out }; static const TCModuleClass tc_framegen_class = { TC_MODULE_CLASS_HEAD(tc_framegen), .init = tc_framegen_init, .fini = tc_framegen_fini, .configure = tc_framegen_configure, .stop = tc_framegen_stop, .inspect = tc_framegen_inspect, .demultiplex = tc_framegen_demultiplex, }; TC_MODULE_ENTRY_POINT(tc_framegen) /*************************************************************************/ /* Old-Style module interface */ /*************************************************************************/ static TCFrameGenPrivateData mod_framegen; static int verbose_flag = TC_QUIET; static int capability_flag = TC_CAP_YUV|TC_CAP_PCM; #define MOD_PRE framegen #define MOD_CODEC "(video) YUV | (audio) PCM" #include "import_def.h" /*************************************************************************/ #define RETURN_WITH_MSG(RET, MSG) do { \ if ((RET) != TC_OK) { \ tc_log_error(MOD_NAME, "%s", (MSG)); \ return RET; \ } \ return TC_OK; \ } while (0) MOD_open { if(param->flag == TC_AUDIO) { param->fd = NULL; mod_framegen.audio_gen = tc_framegen_source_open_audio_pink_noise(vob, 0); RETURN_IF_NULL((mod_framegen.audio_gen), "MOD_open: failed to open the audio frame generator"); return TC_OK; } if(param->flag == TC_VIDEO) { param->fd = NULL; mod_framegen.video_gen = tc_framegen_source_open_video_color_wave(vob, 0); RETURN_IF_NULL((mod_framegen.video_gen), "configure: failed to open the video frame generator"); return TC_OK; } return TC_ERROR; } MOD_decode { int ret; if (param->flag == TC_AUDIO) { ret = tc_framegen_source_get_data(mod_framegen.audio_gen, param->buffer, param->size, ¶m->size); RETURN_WITH_MSG(ret, "MOD_decode: failed to pull a new audio frame"); } if(param->flag == TC_VIDEO) { ret = tc_framegen_source_get_data(mod_framegen.video_gen, param->buffer, param->size, ¶m->size); RETURN_WITH_MSG(ret, "MOD_decode: failed to pull a new video frame"); } return TC_ERROR; } MOD_close { int ret; if(param->flag == TC_AUDIO) { ret = tc_framegen_source_close(mod_framegen.audio_gen); RETURN_WITH_MSG(ret, "MOD_close: failed to close the audio frame generator"); } if(param->flag == TC_VIDEO) { ret = tc_framegen_source_close(mod_framegen.video_gen); RETURN_WITH_MSG(ret, "MOD_close: failed to close the video frame generator"); } return TC_ERROR; } /*************************************************************************/ /* * Local variables: * c-file-style: "stroustrup" * c-file-offsets: ((case-label . *) (statement-case-intro . *)) * indent-tabs-mode: nil * End: * * vim: expandtab shiftwidth=4: */