4 * request video and audio data from MLT and generate an SDI stream
6 * Copyright (C) Broadcasting Center Europe S.A. http://www.bce.lu
7 * an RTL Group Company http://www.rtlgroup.com
10 * E-mail: support_plasec@bce.lu
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
29 * This software act as interface between the MLT Frameworkas as
30 * MLT Consumer and the Linear Systems Ltd. SMPTE 292M and SMPTE 259M-C boards.
32 * Linear Systems can be contacted at http://www.linsys.ca
34 **********************************************************************************
35 * System : INTeL I686 64Bit
36 * OS : Linux SuSE Kernel 2.6.27.39-0.2-default
37 * Compiler: gcc 4.3.2 (c++)
38 **********************************************************************************
39 * Project : MLT SDI Consumer for SD and HD
40 * Started by : Thomas Kurpick, Dipl.Inf. (FH)
41 **********************************************************************************
42 * Supported and tested boards for SD-SDI or HD-SDI Output:
43 * PCI SDI Master™ (model 107)
44 * PCIe SDI Master™ (model 159)
45 * PCIe LP SDI Master™ FD (model 145)
46 * PCIe LP SDI Master™ Quad/o (model 180)
47 * PCIe LP HD-SDI Master™ O (model 193)
49 * Note: PCIe LP HD-SDI Master™ O (model 193) is an VidPort model and supports an
50 * seperate video and audio interface. Device file:
51 * /dev/sdivideotx[] for active video data
52 * /dev/sdiaudiotx[] for pcm audio data
54 * This mlt consumer use the following device files:
55 * /dev/sditx[] (SD-PAL) up to 8 x AES (8 x stereo / 16 audio channels)
56 * /dev/sdivideotx[] (HD)
57 * /dev/sdiaudiotx[] (HD) up to 4 x AES (4 x stereo / 8 audio channels)
60 **********************************************************************************
62 * Thomas Kurpick 08.Jan.2010
64 * Dan Dennedy 10.Feb.2010
66 * See also Git commit log.
68 **********************************************************************************
70 * Consumer properties:
74 * Only to monitor the SDI output a beta version of jpeg-writer is implemented.
75 * 'jpeg_files' a number for output interval
76 * 'save_jpegs' path for image
80 * SDI boards with full frame stream (with blanking):
81 * melt video.dv -consumer sdi:/dev/sditx0
82 * melt video.dv -consumer sdi:/dev/sditx0 blanking=true
83 * melt video.dv -consumer sdi dev_video=/dev/sditx0 blanking=true
84 * melt video.dv audio_index=all -consumer sdi dev_video=/dev/sditx0 blanking=true
86 * SDI boards without full frame stream (without blanking):
87 * melt -profile atsc_1080i_50 video.mpeg audio_index=1 -consumer sdi dev_video=/dev/sdivideotx0 dev_audio=/dev/sdiaudiotx0 blanking=false
88 * melt -profile atsc_1080i_50 video.mpeg audio_index=all -consumer sdi dev_video=/dev/sdivideotx0 dev_audio=/dev/sdiaudiotx0 blanking=false
89 * melt -profile atsc_1080i_50 video.mpeg audio_index=all -consumer sdi dev_video=/dev/sdivideotx0 dev_audio=/dev/sdiaudiotx0 blanking=false jpeg_files=25 save_jpegs=channel_04.jpg
92 * SDI output formats and MLT profiles:
93 * #####################################################################################################################################################
94 * ########## SMPTE 274M 1920 x 1080 Image Sample Structure ############################################################################################
95 * #####################################################################################################################################################
96 * System No. System nomenclature Form of scanning Frame rate Embedded Audio MLT profile Linsys board support (model)
97 * 4 1920 x 1080/60/I interlaced 30 HZ 4 x AES (8 channels) atsc_1080i_60 193
98 * 5 1920 x 1080/59.94/I interlaced 30000/1001 ~ 29.97 HZ 4 x AES (8 channels) atsc_1080i_5994 193
99 * 6 1920 x 1080/50/I interlaced 25 HZ 4 x AES (8 channels) atsc_1080i_50 193
100 * 7 1920 x 1080/30/P progressive 30 HZ 4 x AES (8 channels) atsc_1080p_30 193
101 * 8 1920 x 1080/29.97/P progressive 30000/1001 ~ 29.97 HZ 4 x AES (8 channels) atsc_1080p_2997 193
102 * 9 1920 x 1080/25/P progressive 25 HZ 4 x AES (8 channels) atsc_1080p_25 193
103 * 10 1920 x 1080/24/P progressive 24 HZ 4 x AES (8 channels) atsc_1080p_24 193
104 * 11 1920 x 1080/23.98/P progressive 24000/1001 ~ 23.98 HZ 4 x AES (8 channels) atsc_1080p_2398 193
106 * #####################################################################################################################################################
107 * ########## SMPTE 296M 1280 × 720 Progressive Image Sample Structure #################################################################################
108 * #####################################################################################################################################################
109 * System No. System nomenclature Form of scanning Frame rate Embedded Audio MLT profile Linsys board support (model)
110 * 1 1280 × 720/60 progressive 60 HZ 4 x AES (8 channels) atsc_720p_60 193
111 * 2 1280 × 720/59.94 progressive 60000/1001 ~ 59.97 HZ 4 x AES (8 channels) atsc_720p_5994 193
112 * 3 1280 × 720/50 progressive 50 HZ 4 x AES (8 channels) atsc_720p_50 193
113 * 4 1280 × 720/30 progressive 30 HZ 4 x AES (8 channels) atsc_720p_30 193
114 * 5 1280 × 720/29.97 progressive 30000/1001 ~ 29.97 HZ 4 x AES (8 channels) atsc_720p_2997 193
115 * 6 1280 × 720/25 progressive 25 HZ 4 x AES (8 channels) atsc_720p_25 193
116 * 7 1280 × 720/24 progressive 24 HZ 4 x AES (8 channels) atsc_720p_24 193
117 * 8 1280 × 720/23.98 progressive 24000/1001 ~ 23.98 HZ 4 x AES (8 channels) atsc_720p_2398 193
119 * #####################################################################################################################################################
120 * ########## SMPTE 125M 486i 29.97Hz & BT.656 576i 25Hz ###############################################################################################
121 * #####################################################################################################################################################
122 * System No. System nomenclature Form of scanning Frame rate Embedded Audio MLT profile Linsys board support (model)
123 * SD PAL 720 × 576/50/I interlaced 25 HZ 8 x AES (16 channels) dv_pal 180,145,159,107
124 * SD PAL 720 × 576/50/I interlaced 25 HZ 4 x AES (8 channels) dv_pal 193
125 * SD NTSC 720 × 480/59.94/I interlaced 30000/1001 ~ 29.97 HZ 8 x AES (16 channels) sdi_486i_5994 TODO:180,145,159,107
126 * SD NTSC 720 × 480/59.94/I interlaced 30000/1001 ~ 29.97 HZ 4 x AES (8 channels) sdi_486i_5994 193
130 #include <framework/mlt_consumer.h>
131 #include <framework/mlt_frame.h>
132 #include <framework/mlt_profile.h>
133 #include <framework/mlt_log.h>
134 #include <framework/mlt_events.h>
140 #include <sys/types.h>
141 #include <sys/stat.h>
148 #include "sdi_generator.c"
150 // alias for "struct consumer_SDIstream_s *" , now we can write "consumer_SDIstream". Makes it more readable...
151 typedef struct consumer_SDIstream_s *consumer_SDIstream;
153 struct consumer_SDIstream_s {
154 // Most of these values are set to their defaults by the parent consumer
155 struct mlt_consumer_s parent; // This is the basic Consumer from which we fetch our data
156 mlt_image_format pix_fmt; // Must be mlt_image_yuv422 for SDI
161 struct audio_format audio_format;
168 char *device_file_video; // Path for SDI output
169 char *device_file_audio; // Path for exlusive SDI audio output
172 * write own HANC (ancillary data) is available for:
173 * SDI board ASSY 193 HD 'blanking=false'
174 * SDI board ASSY 180 SD quad 'blanking=true'
175 * SDI board ASSY 145 SD single 'blanking=true'
182 // our audio channel pair for this frame
183 int16_t audio_buffer[MAX_AUDIO_STREAMS][MAX_AUDIO_SAMPLES]; // The SDI audio channel pairs for this frame
185 char *video_fmt_name; // 1080i25, 1080p25, 576i50, 480i2997, ...
190 * Forward references to static functions.
192 static int consumer_start(mlt_consumer this);
193 static int consumer_stop(mlt_consumer this);
194 static int consumer_is_stopped(mlt_consumer this);
195 static void consumer_close(mlt_consumer parent);
196 static void *consumer_thread(void *);
198 static void consumer_write_JPEG(char * path, uint8_t **vBuffer, mlt_profile myProfile);
199 int convertYCBCRtoRGB(int y1, int cb, int cr, int y2, uint8_t * target_rgb);
201 /*****************************************************************************************************
202 ****************************************** SDI Master Consumer **************************************
203 *****************************************************************************************************/
205 /** This is what will be called by the factory
206 * @param profile: profile name for consumer
207 * @param type: unused
209 * @param *arg: pointer to output path
211 mlt_consumer consumer_SDIstream_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) {
213 // Create the consumer object
214 consumer_SDIstream this = calloc(sizeof(struct consumer_SDIstream_s), 1);
216 // If malloc and consumer init ok
217 if (this != NULL && mlt_consumer_init(&this->parent, this, profile) == 0) {
219 // Get the parent consumer object
220 mlt_consumer parent = &this->parent;
222 // We have stuff to clean up, so override the close method
223 parent->close = consumer_close;
225 // Set output path for SDI, default is "/dev/sditx0"
227 this->device_file_video = strdup("/dev/sditx0");
229 this->device_file_video = strdup(arg);
232 // Set up start/stop/terminated callbacks
233 parent->start = consumer_start;
234 parent->stop = consumer_stop;
235 parent->is_stopped = consumer_is_stopped;
237 // Set explicit to zero or other value
239 for (i = 0; i < MAX_AUDIO_STREAMS; i++) {
240 for (j = 0; j < MAX_AUDIO_SAMPLES; j++) {
241 this->audio_buffer[i][j] = j;
245 mlt_events_register( MLT_CONSUMER_PROPERTIES(parent), "consumer-fatal-error", NULL );
247 // Return the consumer produced
251 // malloc or consumer init failed
259 * Start the consumer.
261 static int consumer_start(mlt_consumer parent) {
263 // Get the properties
264 mlt_properties properties = mlt_consumer_properties(parent);
266 // Get the actual object
267 consumer_SDIstream this = parent->child;
269 // Check that we're not already running
270 if (!mlt_properties_get_int(properties, "running")) {
272 pthread_t *consumer_pthread = calloc(1, sizeof(pthread_t));
274 // Assign the thread to properties
275 mlt_properties_set_data(properties, "consumer_pthread", consumer_pthread, sizeof(pthread_t), free, NULL);
277 // Set the running state
278 mlt_properties_set_int(properties, "running", 1);
280 // Create the the threads
281 pthread_create(consumer_pthread, NULL, consumer_thread, this);
289 static int consumer_stop(mlt_consumer parent) {
291 // Get the properties
292 mlt_properties properties = mlt_consumer_properties(parent);
294 // Check that we're running
295 if (mlt_properties_get_int(properties, "running")) {
298 pthread_t *consumer_pthread = mlt_properties_get_data(properties, "consumer_pthread", NULL);
301 mlt_properties_set_int(properties, "running", 0);
303 // Wait for termination
304 pthread_join(*consumer_pthread, NULL);
311 * Determine if the consumer is stopped
313 static int consumer_is_stopped(mlt_consumer this) {
314 // Get the properties
315 mlt_properties properties = mlt_consumer_properties(this);
316 return !mlt_properties_get_int(properties, "running");
320 * Threaded wrapper for pipe.
322 static void *consumer_thread(void *arg) {
325 consumer_SDIstream this = arg;
328 mlt_consumer consumer = &this->parent;
330 // Convenience functionality (this is to stop melt/inigo after the end of a playout)
331 int terminate_on_pause = mlt_properties_get_int(MLT_CONSUMER_PROPERTIES( consumer ), "terminate_on_pause");
332 int terminated = 0; // save ony status
334 int save_jpegs = mlt_properties_get_int(MLT_CONSUMER_PROPERTIES( consumer ), "save_jpegs");
335 char * jpeg_folder = mlt_properties_get(MLT_CONSUMER_PROPERTIES( consumer ), "jpeg_file");
337 // If no folder is specified, skip jpeg export
338 if (jpeg_folder == NULL) {
343 mlt_log_info(MLT_CONSUMER_SERVICE(consumer), "Saving a JPEG every %i frame.\n", save_jpegs);
345 int counter = 0; // each second we save a Jpeg
347 // set properties (path) for device files
348 if (mlt_properties_get(MLT_CONSUMER_PROPERTIES( consumer ), "dev_video") != NULL) {
349 this->device_file_video = strdup(mlt_properties_get(MLT_CONSUMER_PROPERTIES( consumer ), "dev_video"));
351 if (mlt_properties_get(MLT_CONSUMER_PROPERTIES( consumer ), "dev_audio") != NULL) {
352 if (this->blanking == 0) {
353 this->device_file_audio = strdup(mlt_properties_get(MLT_CONSUMER_PROPERTIES( consumer ), "dev_audio"));
355 // if we write HANC we do not write further audio data
356 mlt_log_info(MLT_CONSUMER_SERVICE(consumer), "Audio device file is set but will not be used.\n");
360 // Set additional device file defaults
362 int fd = stat(this->device_file_video, &st);
364 if (this->device_file_video)
365 free(this->device_file_video);
366 this->device_file_video = strdup("/dev/sdivideotx0");
370 if (this->device_file_audio) {
371 fd = stat(this->device_file_audio, &st);
373 if (this->device_file_audio)
374 free(this->device_file_audio);
375 this->device_file_audio = strdup("/dev/sdiaudiotx0");
379 } else if (strstr(this->device_file_video, "sdivideotx")) {
380 if (this->device_file_audio)
381 free(this->device_file_audio);
382 this->device_file_audio = strdup("/dev/sdiaudiotx0");
385 // set blanking flag; is not nessary we write no own blanking(HANC) for HD board ASSY 193
386 if (mlt_properties_get(MLT_CONSUMER_PROPERTIES( consumer ), "blanking")) {
388 if (!strcmp( mlt_properties_get(MLT_CONSUMER_PROPERTIES( consumer ), "blanking"), "false")) {
390 } else if (!strcmp( mlt_properties_get(MLT_CONSUMER_PROPERTIES( consumer ), "blanking"), "true")) {
393 this->blanking = mlt_properties_get_int(MLT_CONSUMER_PROPERTIES( consumer ), "blanking");
395 } else if (strstr(this->device_file_video, "sdivideotx")) {
398 // set default value without HD board, also with blanking
402 // Define a frame pointer
405 // set Datablock number for SDI encoding
408 double fps = mlt_properties_get_double(MLT_CONSUMER_PROPERTIES(consumer), "fps");
409 unsigned int count = 0;
411 // Tell the framework how we want our audio and video
412 int frequency = this->audio_format.sample_rate;
413 int channels = mlt_properties_get_int( MLT_CONSUMER_PROPERTIES(consumer), "channels" );
416 // set number of audio channels, linsys vidport model 193 is limited to 8 channels (4AES frames)
417 this->audio_format.channels = 8; /* 0,2,4,6,8 */
418 this->audio_format.aformat = mlt_audio_s16; /* 16, 24, 32 */
419 this->audio_format.sample_rate = 48000;
420 this->pix_fmt = mlt_image_yuv422;
422 if (!sdi_init(this->device_file_video, this->device_file_audio, this->blanking, mlt_service_profile((mlt_service) consumer), &this->audio_format)) {
423 mlt_log_fatal( MLT_CONSUMER_SERVICE(consumer), "failed to initialize\n" );
424 mlt_events_fire( MLT_CONSUMER_PROPERTIES(consumer), "consumer-fatal-error", NULL );
427 uint8_t *video_buffer;
428 int16_t *audio_buffer_tmp; // the upstream audio buffer
430 // Loop until told not to
431 while (!consumer_is_stopped(consumer) && terminated == 0) { //
433 // Get a frame from the service
434 if ((frame = mlt_consumer_rt_frame(consumer)) != NULL) {
436 // Check for termination
437 if (terminate_on_pause && frame != NULL) {
438 terminated = mlt_properties_get_double(MLT_FRAME_PROPERTIES( frame ), "_speed") == 0.0;
439 if (terminated == 1) {
440 mlt_log_verbose(MLT_CONSUMER_SERVICE(consumer), "\nEnd of playout reached, terminating\n");
441 consumer_stop(consumer);
445 // True if mlt_consumer_rt_frame(...) successful
446 if (mlt_properties_get_int(mlt_frame_properties(frame), "rendered") == 1) {
448 // Get the video from this frame and save it to our video_buffer
449 mlt_frame_get_image(frame, &video_buffer, &this->pix_fmt, &this->width, &this->height, 1);
451 // Get the audio from this frame and save it to our audio_buffer
452 samples = mlt_sample_calculator(fps, frequency, count++);
453 mlt_frame_get_audio(frame, (void**) &audio_buffer_tmp, &this->audio_format.aformat, &frequency, &channels, &samples);
455 this->audio_format.sample_rate = frequency;
456 this->audio_format.samples = samples;
458 /* TODO: Audio is currently hard coded to 8 channels because write 8 channels to the sdi board.
459 The Linys SDI board has to be configured with the same number of channels!
460 this->audio_format.channels = channels; // take given number of channels
463 /* Tell the sdi_generator.c to playout our frame
464 * 8 AES (8 x stereo channels are possible, max. 16 channels) Linsys SD board model: 107, 159, 145, 180
465 * 4 AES (4 x stereo channels are possible, max. 8 channels) Linsys HD board model: 193
469 // provide mapping of audio channels
471 int map_channels, map_start;
473 for (i = 0; i < MAX_AUDIO_STREAMS && j < channels; i++) {
477 sprintf(key, "meta.map.audio.%d.channels", i);
478 map_channels = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), key);
479 sprintf(key, "meta.map.audio.%d.start", i);
480 map_start = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), key);
483 map_channels = channels - j;
484 for (c = 0; c < map_channels && j < channels; c++, j++) {
485 int16_t *src = audio_buffer_tmp + j;
486 int16_t *dest = this->audio_buffer[(map_start + c) / 2] + (map_start + c) % 2;
497 // generate SDI frame and playout
498 my_dbn = sdi_playout(video_buffer, this->audio_buffer, &this->audio_format, (channels + 1) / 2, my_dbn);
500 // write a JPEG of every X-th frame
501 if (save_jpegs > 0 && counter >= save_jpegs) {
502 consumer_write_JPEG(jpeg_folder, &video_buffer, mlt_service_profile((mlt_service) consumer));
504 } else if (save_jpegs > 0) {
507 mlt_events_fire(MLT_CONSUMER_PROPERTIES( consumer ), "consumer-frame-show", frame, NULL );
509 mlt_log_warning(MLT_CONSUMER_SERVICE(consumer), "Videobuffer was NULL, skipping playout!\n");
512 mlt_log_warning(MLT_CONSUMER_SERVICE(consumer), "WARNING the requested frame is not yet rendered! This will cause image disturbance!\n");
515 my_dbn = sdi_playout(video_buffer, this->audio_buffer, &this->audio_format, (channels + 1) / 2, my_dbn);
517 mlt_log_warning(MLT_CONSUMER_SERVICE(consumer), "Videobuffer was NULL, skipping playout!\n");
522 mlt_frame_close(frame);
530 * Callback to allow override of the close method.
532 static void consumer_close(mlt_consumer parent) {
534 // Get the actual object
535 consumer_SDIstream this = parent->child;
537 free(this->device_file_video);
538 free(this->device_file_audio);
540 // Now clean up the rest (the close = NULL is a bit nasty but needed for now)
541 parent->close = NULL;
542 mlt_consumer_close(parent);
544 // Invoke the close function of the sdi_generator to close opened files used for output
547 // Finally clean up this
552 * Write videobuffer as JPEG to path
555 static void consumer_write_JPEG(char * filename, uint8_t **vBuffer, mlt_profile myProfile) {
559 int bytes_per_pixel = 3; // or 1 for GRACYSCALE images
560 int color_space = JCS_RGB; // or JCS_GRAYSCALE for grayscale images
562 uint8_t * buffer_position = *vBuffer;
563 uint8_t image_rgb[myProfile->width * myProfile->height * bytes_per_pixel];
565 //convert vBuffer to RGB
567 for (i = 0; i < sizeof(image_rgb) / 6; i++) {
568 int y1 = *(buffer_position++);
569 int cb = *(buffer_position++);
570 int y2 = *(buffer_position++);
571 int cr = *(buffer_position++);
572 convertYCBCRtoRGB(y1, cb, cr, y2, &image_rgb[i * 6]);
575 struct jpeg_compress_struct cinfo;
576 struct jpeg_error_mgr jerr;
578 // this is a pointer to one row of image data
579 JSAMPROW row_pointer[1];
581 FILE *outfile = fopen(filename, "wb");
584 mlt_log_error(NULL, "%s: Error opening output jpeg file %s\n!", __FILE__, filename);
587 cinfo.err = jpeg_std_error(&jerr);
588 jpeg_create_compress(&cinfo);
589 jpeg_stdio_dest(&cinfo, outfile);
591 // Setting the parameters of the output file here
592 cinfo.image_width = myProfile->width;
593 cinfo.image_height = myProfile->height;
594 cinfo.input_components = bytes_per_pixel;
595 cinfo.in_color_space = (J_COLOR_SPACE) color_space;
597 // default compression parameters, we shouldn't be worried about these
598 jpeg_set_defaults(&cinfo);
600 // Now do the compression
601 jpeg_start_compress(&cinfo, TRUE);
603 // like reading a file, this time write one row at a time
604 while (cinfo.next_scanline < cinfo.image_height) {
605 row_pointer[0] = &image_rgb[cinfo.next_scanline * cinfo.image_width * cinfo.input_components];
606 jpeg_write_scanlines(&cinfo, row_pointer, 1);
608 // similar to read file, clean up after we're done compressing
609 jpeg_finish_compress(&cinfo);
610 jpeg_destroy_compress(&cinfo);
616 * converts YCbCr samples to two 32-bit RGB values
617 * @param y1 cb cr and y2 values
618 * @param target pointer
619 * @return 0 upon success
621 int convertYCBCRtoRGB(int y1, int cb, int cr, int y2, uint8_t * target_rgb) {
645 uint8_t r1, g1, b1, r2, g2, b2;
647 //pointer to current output buffer position
648 uint8_t * target_pointer = target_rgb;
650 r1 = y1 + 1.402 * (cr - 128);
651 g1 = y1 - 0.34414 * (cb - 128) - 0.71414 * (cr - 128);
652 b1 = y1 + 1.772 * (cb - 128);
654 r2 = y2 + 1.402 * (cr - 128);
655 g2 = y2 - 0.34414 * (cb - 128) - 0.71414 *(cr - 128);
656 b2 = y2 + 1.772 * (cb - 128);
658 *target_pointer++ = r1;
659 *target_pointer++ = g1;
660 *target_pointer++ = b1;
662 *target_pointer++ = r2;
663 *target_pointer++ = g2;
664 *target_pointer++ = b2;