UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
file.c
Go to the documentation of this file.
1
5/*
6 * Copyright (c) 2023 CESNET, z. s. p. o.
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, is permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * 3. Neither the name of CESNET nor the names of its contributors may be
21 * used to endorse or promote products derived from this software without
22 * specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
26 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
27 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
28 * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
29 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
30 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
34 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
35 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 */
46#include <assert.h>
47#include <libavcodec/avcodec.h>
48#include <libavformat/avformat.h>
49#include <libavutil/channel_layout.h>
50#include <pthread.h>
51
52#include "audio/types.h"
53#include "audio/utils.h"
54#include "debug.h"
55#include "lib_common.h"
58#include "libavcodec/utils.h"
59#include "tv.h"
60#include "types.h"
61#include "utils/color_out.h"
62#include "utils/fs.h"
63#include "utils/macros.h"
64#include "utils/misc.h"
65#include "utils/text.h"
66#include "video.h"
67#include "video_display.h"
68
69#define DEFAULT_FILENAME "out.mp4"
70#define DEFAULT_PIXEL_FORMAT AV_PIX_FMT_YUV420P
71#define MOD_NAME "[File disp.] "
72
73enum {
74 DEFAULT_MAX_AV_DIFF_NS = 84 * MS_IN_NS, // 2 24p frames
75};
76
78 AVStream *st;
79 AVCodecContext *enc;
80 long long int next_pts;
82 union {
84 AVFrame *aud_frm;
85 };
86};
87
88struct state_file {
89 AVFormatContext *format_ctx;
90 bool is_nut; // == use RAW
97 pthread_t thread_id;
98 pthread_mutex_t lock;
99 pthread_cond_t cv;
102};
103
104static void *worker(void *arg);
105
106static void
107display_file_probe(struct device_info **available_cards, int *count,
108 void (**deleter)(void *))
109{
110 *deleter = free;
111 *count = 1;
112 *available_cards = calloc(*count, sizeof **available_cards);
113 snprintf((*available_cards)[0].name, sizeof(*available_cards)[0].name,
114 "file");
115}
116
117static void
119{
120 struct state_file *s = state;
121 if (s->should_exit) { // thread started
122 pthread_join(s->thread_id, NULL);
123 pthread_mutex_destroy(&s->lock);
124 pthread_cond_destroy(&s->cv);
125 }
126 if (s->initialized) {
127 av_write_trailer(s->format_ctx);
128 }
129 avcodec_free_context(&s->video.enc);
130 avcodec_free_context(&s->audio.enc);
131 if (!(s->format_ctx->oformat->flags & AVFMT_NOFILE)) {
132 avio_closep(&s->format_ctx->pb);
133 }
135 av_frame_free(&s->audio.aud_frm);
137 free(s);
138}
139
140static void
141usage(bool full)
142{
143 color_printf("Display " TBOLD("file") " syntax:\n\n");
144 color_printf("\t" TBOLD(TRED("file") "[:name=<filename>]") " | " TBOLD(
145 "file:[full]help") "\n\n");
146 color_printf("where\n\n");
147 color_printf("\t" TBOLD("<filename>") " - output file name\n");
148 if (full) {
150 "\t" TBOLD("max_av_diff") " - allowed A/V descync length "
151 "(in seconds; default %.3f)\n",
153 }
154 color_printf("\n");
155 char codec_note[] = TBOLD(
156 "NUT") " files are written uncompressed. For other file "
157 "formats " TBOLD(
158 "FFmpeg") " container default "
159 "codecs are used.\n\nDefault output file "
160 "is: " TBOLD(DEFAULT_FILENAME) "\n\n";
161 color_printf("%s", wrap_paragraph(codec_note));
162}
163
164static bool
165parse_fmt(struct state_file *s, char *fmt)
166{
167 char *end_ptr = NULL;
168 char *item = NULL;
169 while ((item = strtok_r(fmt, ":", &end_ptr))) {
170 fmt = NULL;
171 char *val = strchr(item, '=') + 1;
172 if (IS_KEY_PREFIX(item, "file") || IS_KEY_PREFIX(item, "name")) {
173 snprintf(s->filename, sizeof s->filename, "%s", val);
174 } else if (IS_KEY_PREFIX(item, "max_av_diff")) {
175 s->max_av_diff_ns =
176 (time_ns_t) (strtod(val, NULL) * NS_IN_SEC_DBL);
177 assert(s->max_av_diff_ns >= 0.0);
178 } else {
180 MOD_NAME "Unknown option: %s\n", item);
181 return false;
182 }
183 }
184 return true;
185}
186
187static void *
188display_file_init(struct module *parent, const char *fmt, unsigned int flags)
189{
190 UNUSED(parent);
191 if (!strcmp(fmt, "help") || !strcmp(fmt, "fullhelp")) {
192 usage(!strcmp(fmt, "fullhelp"));
193 return INIT_NOERR;
194 }
195
196 struct state_file *s = calloc(1, sizeof *s);
197 snprintf(s->filename, sizeof s->filename, "%s", DEFAULT_FILENAME);
199 char *fmt_c = strdup(fmt);
200 bool parse_ret = parse_fmt(s, fmt_c);
201 free(fmt_c);
202 if (!parse_ret) {
203 free(s);
204 return NULL;
205 }
206
208 avformat_alloc_output_context2(&s->format_ctx, NULL, NULL, s->filename);
209 if (s->format_ctx == NULL) {
210 log_msg(LOG_LEVEL_WARNING, "Could not deduce output format "
211 "from file extension, using NUT.\n");
212 avformat_alloc_output_context2(&s->format_ctx, NULL, "nut",
213 s->filename);
214 assert(s->format_ctx != NULL);
215 }
216 s->is_nut = !strcmp(s->format_ctx->oformat->name, "nut");
217 s->video.st = avformat_new_stream(s->format_ctx, NULL);
218 s->video.st->id = 0;
219
220 if (!(s->format_ctx->oformat->flags & AVFMT_NOFILE)) {
221 int ret =
222 avio_open(&s->format_ctx->pb, s->filename, AVIO_FLAG_WRITE);
223 if (ret < 0) {
224 error_msg(MOD_NAME "avio_open %s: %s\n", s->filename,
225 av_err2str(ret));
227 return NULL;
228 }
229 }
230
231 int ret = pthread_mutex_init(&s->lock, NULL);
232 ret |= pthread_cond_init(&s->cv, NULL);
233 ret |= pthread_create(&s->thread_id, NULL, worker, s);
234 assert(ret == 0);
235
236 if ((flags & DISPLAY_FLAG_AUDIO_ANY) != 0U) {
237 s->audio.st = avformat_new_stream(s->format_ctx, NULL);
238 s->audio.st->id = 1;
239 }
240
241 return s;
242}
243
244static void
246{
247 AVFrame *avfrm = frame->callbacks.dispose_udata;
248 av_frame_free(&avfrm);
249}
250
251static enum AVPixelFormat
253{
254 if (!is_nut) {
256 }
257 if (ug_codec == R10k) {
258 return AV_PIX_FMT_GBRP10LE;
259 }
260 if (ug_codec == R12L) {
261 return AV_PIX_FMT_GBRP12LE;
262 }
263 if (ug_codec == v210) {
264 return AV_PIX_FMT_YUV422P10LE;
265 }
267}
268
269static struct video_frame *
271{
272 struct state_file *s = state;
273
276 return vf_alloc_desc_data(s->video_desc); // conv needed
277 }
278 AVFrame *frame = av_frame_alloc();
280 frame->width = (int) s->video_desc.width;
281 frame->height = (int) s->video_desc.height;
282 int ret = av_frame_get_buffer(frame, 0);
283 if (ret < 0) {
284 error_msg(MOD_NAME "Could not allocate frame data: %s.\n",
285 av_err2str(ret));
286 av_frame_free(&frame);
287 return NULL;
288 }
289 struct video_frame *out = vf_alloc_desc(s->video_desc);
290 out->tiles[0].data = (char *) frame->data[0];
293 return out;
294}
295
296static bool
297display_file_putf(void *state, struct video_frame *frame, long long timeout_ns)
298{
299 if (timeout_ns == PUTF_DISCARD) {
300 vf_free(frame);
301 return true;
302 }
303 struct state_file *s = state;
304 pthread_mutex_lock(&s->lock);
305 if (frame == NULL) {
306 s->should_exit = true;
307 pthread_mutex_unlock(&s->lock);
308 pthread_cond_signal(&s->cv);
309 return true;
310 }
311 bool ret = true;
312 if (s->video.vid_frm != NULL) {
313 log_msg(LOG_LEVEL_WARNING, MOD_NAME "Video frame dropped!\n");
315 ret = false;
316 }
317 s->video.vid_frm = frame;
318 pthread_mutex_unlock(&s->lock);
319 pthread_cond_signal(&s->cv);
320 return ret;
321}
322
323static bool
324avp_is_in_set(enum AVPixelFormat needle, int nmembers,
325 const enum AVPixelFormat *haystick)
326{
327 for (int i = 0; i < nmembers; ++i) {
328 if (haystick[i] == needle) {
329 return true;
330 }
331 }
332 return false;
333}
334
335static bool
336display_file_get_property(void *state, int property, void *val, size_t *len)
337{
338 struct state_file *s = state;
339
340 switch (property) {
343 int count = 0;
344 if (s->is_nut) {
345 codecs[count++] = R10k;
346 codecs[count++] = R12L;
347 codecs[count++] = v210;
348 }
349 for (int i = 0; i < VIDEO_CODEC_COUNT; ++i) {
350 if (s->is_nut) {
351 if (get_ug_to_av_pixfmt(i) != AV_PIX_FMT_NONE) {
352 codecs[count++] = i;
353 }
354 } else {
355 enum AVPixelFormat fmts[AV_PIX_FMT_NB];
356 struct to_lavc_req_prop prop = {
358 };
359 const int nm =
360 get_available_pix_fmts(i, prop, fmts);
362 fmts)) {
363 codecs[count++] = i;
364 }
365 }
366 }
367 const size_t c_len = count * sizeof codecs[0];
368 assert(c_len <= *len);
369 memcpy(val, codecs, c_len);
370 *len = c_len;
371 break;
372 }
374 struct audio_desc *desc = val;
375 assert(*len == (int) sizeof *desc);
376 desc->codec = AC_PCM;
377 if (!s->is_nut) {
378 desc->ch_count = MIN(2, desc->ch_count);
379 desc->bps = 2;
380 }
381 break;
382 }
383 default:
384 return false;
385 }
386 return true;
387}
388
389static bool
391{
392 struct state_file *s = state;
393
394 s->video_desc = desc;
395 return true;
396}
397
398static void
399aud_ctx_set_ch_layout(struct AVCodecContext *ctx, int ch_count, bool raw)
400{
401#if FF_API_NEW_CHANNEL_LAYOUT
402 if (raw) {
403 ctx->ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_MASK(
404 ch_count, (1 << ch_count) - 1);
405 return;
406 }
407 av_channel_layout_default(&ctx->ch_layout, ch_count);
408#else
409 (void) raw;
410 ctx->channel_layout = av_get_default_channel_layout(ch_count);
411#endif
412}
413
414static void
415aud_frm_set_ch_layout(AVFrame *frm, int ch_count)
416{
417#if FF_API_NEW_CHANNEL_LAYOUT
418 frm->ch_layout = (AVChannelLayout) AV_CHANNEL_LAYOUT_MASK(
419 ch_count, (1 << ch_count) - 1);
420#else
421 frm->channel_layout = av_get_default_channel_layout(ch_count);
422#endif
423}
424
425static AVFrame *
426alloc_audio_frame(struct audio_desc desc, int nb_samples,
427 enum AVSampleFormat fmt)
428{
429 AVFrame *av_frm = av_frame_alloc();
430 av_frm->format = fmt;
431 aud_frm_set_ch_layout(av_frm, desc.ch_count);
432 av_frm->sample_rate = desc.sample_rate;
433 av_frm->nb_samples = nb_samples;
434
435 if (nb_samples == 0) {
436 return av_frm;
437 }
438 int ret = av_frame_get_buffer(av_frm, 0);
439 if (ret < 0) {
440 error_msg(MOD_NAME "audio buf alloc: %s\n", av_err2str(ret));
441 av_frame_free(&av_frm);
442 return NULL;
443 }
444 return av_frm;
445}
446
447static enum AVSampleFormat
448select_sample_format(const enum AVSampleFormat *fmts)
449{
450 enum AVSampleFormat fmt = AV_SAMPLE_FMT_NONE;
451 while (*fmts) {
452 if (*fmts == AV_SAMPLE_FMT_S16P || *fmts == AV_SAMPLE_FMT_S16) {
453 return *fmts;
454 }
455 if (*fmts == AV_SAMPLE_FMT_FLTP) { // use fltp only if no s16[p]
456 fmt = *fmts;
457 }
458 fmts++;
459 }
460 assert(fmt != AV_SAMPLE_FMT_NONE && MOD_NAME
461 "Only S16, S16P and FLTP samples are currently "
462 "handled, please report!");
463 return fmt;
464}
465
466static bool
467configure_audio(struct state_file *s, struct audio_desc aud_desc,
468 AVFrame **tmp_frame)
469{
470 enum AVCodecID codec_id = s->format_ctx->oformat->audio_codec;
471 if (s->is_nut) {
472 switch (aud_desc.bps) {
473 case 1:
474 codec_id = AV_CODEC_ID_PCM_U8;
475 break;
476 case 2:
477 codec_id = AV_CODEC_ID_PCM_S16LE;
478 break;
479 case 3:
480 case 4:
481 codec_id = AV_CODEC_ID_PCM_S32LE;
482 break;
483 default:
484 abort();
485 }
486 }
487 const AVCodec *codec = avcodec_find_encoder(codec_id);
488 if (codec == NULL && !s->is_nut) {
489 codec = avcodec_find_encoder(codec_id);
490 }
491 if (codec == NULL) {
492 error_msg(MOD_NAME "Unable to find audio encoder for %s\n",
493 avcodec_get_name(codec_id));
494 return false;
495 }
496 s->audio.enc = avcodec_alloc_context3(codec);
497 s->audio.enc->sample_fmt =
498 s->is_nut ? audio_bps_to_av_sample_fmt(aud_desc.bps, false)
499 : select_sample_format(s->audio.enc->codec->sample_fmts);
501 s->audio.enc->sample_rate = aud_desc.sample_rate;
502 s->audio.st->time_base = (AVRational){ 1, aud_desc.sample_rate };
503 s->video.enc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
504
505 int ret = avcodec_open2(s->audio.enc, codec, NULL);
506 if (ret < 0) {
507 error_msg(MOD_NAME "audio avcodec_open2: %s\n",
508 av_err2str(ret));
509 return false;
510 }
511
512 ret = avcodec_parameters_from_context(s->audio.st->codecpar,
513 s->audio.enc);
514 if (ret < 0) {
516 "Could not copy audio stream parameters: %s\n",
517 av_err2str(ret));
518 return false;
519 }
520
521 av_frame_free(tmp_frame);
522 *tmp_frame = alloc_audio_frame(
523 aud_desc, s->audio.enc->frame_size,
524 s->audio.enc->sample_fmt);
525 (*tmp_frame)->nb_samples = 0;
526
527 return true;
528}
529
530static bool
531configure_video(struct state_file *s, struct video_desc vid_desc)
532{
533 s->video.st->time_base = (AVRational){ get_framerate_d(vid_desc.fps),
534 get_framerate_n(vid_desc.fps) };
535 const enum AVCodecID codec_id =
536 s->is_nut ? AV_CODEC_ID_RAWVIDEO
537 : s->format_ctx->oformat->video_codec;
538 const AVCodec *codec = avcodec_find_encoder(codec_id);
539 if (codec == NULL) {
540 error_msg(MOD_NAME "Unable to find video encoder for %s\n",
541 avcodec_get_name(codec_id));
542 return false;
543 }
544 s->video.enc = avcodec_alloc_context3(codec);
545 s->video.enc->width = (int) vid_desc.width;
546 s->video.enc->height = (int) vid_desc.height;
547 s->video.enc->time_base = s->video.st->time_base;
548 s->video.enc->pix_fmt =
549 file_get_pix_fmt(s->is_nut, vid_desc.color_spec);
550 s->video.enc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
551 av_opt_set(s->video.enc->priv_data, "preset", "ultrafast", 0); // x264/5
552 av_opt_set(s->video.enc->priv_data, "deadline", "realtime", 0); // vp9
553 av_opt_set(s->video.enc->priv_data, "cpu-used", "8", 0);
554 int ret = avcodec_open2(s->video.enc, codec, NULL);
555 if (ret < 0) {
556 error_msg(MOD_NAME "video avcodec_open2: %s\n",
557 av_err2str(ret));
558 return false;
559 }
560 ret = avcodec_parameters_from_context(s->video.st->codecpar,
561 s->video.enc);
562 if (ret < 0) {
564 "Could not copy video stream parameters: %s\n",
565 av_err2str(ret));
566 return false;
567 }
568 if (s->video.enc->pix_fmt != get_ug_to_av_pixfmt(vid_desc.color_spec)) {
570 vid_desc.color_spec, (int) vid_desc.width,
571 (int) vid_desc.height, s->video.enc->pix_fmt,
573 }
574 return true;
575}
576
577static bool
578initialize(struct state_file *s, struct video_desc *saved_vid_desc,
579 const struct video_frame *vid_frm, struct audio_desc *saved_aud_desc,
580 const AVFrame *aud_frm, AVFrame **tmp_aud_frame)
581{
582 if (!vid_frm || (s->audio.st != NULL && !aud_frm)) {
584 MOD_NAME "Waiting for all streams to init.\n");
585 return false;
586 }
587
588 // video
589 const struct video_desc vid_desc = video_desc_from_frame(vid_frm);
590 if (!configure_video(s, vid_desc)) {
591 exit_uv(1);
592 return false;
593 }
594 *saved_vid_desc = vid_desc;
595
596 // audio
597 if (aud_frm != NULL) {
598 const struct audio_desc aud_desc =
600 if (!configure_audio(s, aud_desc, tmp_aud_frame)) {
601 exit_uv(1);
602 return false;
603 }
604 *saved_aud_desc = aud_desc;
605 }
606
607 av_dump_format(s->format_ctx, 0, s->filename, 1);
608
609 int ret = avformat_write_header(s->format_ctx, NULL);
610 if (ret < 0) {
612 "Error occurred when opening output file: %s\n",
613 av_err2str(ret));
614 return false;
615 }
616
617 s->initialized = true;
618 return true;
619}
620
621static void
622write_frame(AVFormatContext *format_ctx, struct output_stream *ost,
623 AVFrame *frame, AVPacket *pkt)
624{
625 frame->pts = ost->next_pts;
626 int ret = avcodec_send_frame(ost->enc, frame);
627 if (ret < 0) {
628 error_msg(MOD_NAME "avcodec_send_frame: %s\n",
629 av_err2str(ret));
630 return;
631 }
632 while (ret >= 0) {
633 ret = avcodec_receive_packet(ost->enc, pkt);
634 if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
635 break;
636 }
637 if (ret < 0) {
638 error_msg(MOD_NAME "video avcodec_receive_frame: %s\n",
639 av_err2str(ret));
640 return;
641 }
642 av_packet_rescale_ts(pkt, ost->enc->time_base,
643 ost->st->time_base);
644 pkt->stream_index = ost->st->index;
645 ret = av_interleaved_write_frame(format_ctx, pkt);
646 if (ret < 0) {
647 error_msg(MOD_NAME "error writting video packet: %s\n",
648 av_err2str(ret));
649 }
650 }
651}
652
653static bool
654check_reconf(struct video_desc *saved_vid_desc,
655 const struct video_frame *vid_frm, struct audio_desc *saved_aud_desc,
656 const AVFrame *aud_frm)
657{
658 if (vid_frm != NULL) {
659 const struct video_desc cur_vid_desc =
660 video_desc_from_frame(vid_frm);
661 if (!video_desc_eq(*saved_vid_desc, cur_vid_desc)) {
662 return false;
663 }
664 }
665 if (aud_frm) {
666 const struct audio_desc cur_aud_desc =
668 if (!audio_desc_eq(*saved_aud_desc, cur_aud_desc)) {
669 return false;
670 }
671 }
672 return true;
673}
674
676static void
677file_append_audio_frame(AVFrame *dst, const AVFrame *src, int skip_in_samples,
678 int nb_samples)
679{
680#if FF_API_NEW_CHANNEL_LAYOUT
681 const int ch_count = src->ch_layout.nb_channels;
682#else
683 const int ch_count =
684 av_get_channel_layout_nb_channels(src->channel_layout);
685#endif
686 const int s_bps = av_get_bytes_per_sample(src->format);
687 const int d_bps = av_get_bytes_per_sample(dst->format);
688
689 if (av_sample_fmt_is_planar(dst->format)) {
690 char *dst_data[ch_count];
691 for (int i = 0; i < ch_count; ++i) {
692 dst_data[i] = (char *) dst->data[i] +
693 (ptrdiff_t) dst->nb_samples * d_bps;
694 }
695 if (dst->format == AV_SAMPLE_FMT_FLTP) {
697 dst_data,
698 (char *) src->data[0] +
699 (ptrdiff_t) skip_in_samples * s_bps * ch_count,
700 s_bps, nb_samples * s_bps * ch_count, ch_count);
701 } else {
702 assert(s_bps == d_bps);
704 dst_data,
705 (char *) src->data[0] +
706 (ptrdiff_t) skip_in_samples * s_bps * ch_count,
707 s_bps, nb_samples * s_bps * ch_count, ch_count);
708 }
709
710 } else {
711 assert(s_bps == d_bps);
712 memcpy(dst->data[0] +
713 (ptrdiff_t) dst->nb_samples * s_bps * ch_count,
714 src->data[0] +
715 (ptrdiff_t) skip_in_samples * s_bps * ch_count,
716 (size_t) nb_samples * s_bps * ch_count);
717 }
718 dst->nb_samples += nb_samples;
719}
720
721static void
722write_audio_frame(struct state_file *s, AVFrame *aud_frm, AVFrame *tmp_frm,
723 AVPacket *pkt)
724{
727 (aud_frm->nb_samples * NS_IN_SEC / aud_frm->sample_rate);
728
729 if (s->is_nut) {
730 write_frame(s->format_ctx, &s->audio, aud_frm, pkt);
731 s->audio.next_pts += aud_frm->nb_samples;
732 return;
733 }
734
735 // compressed audio
736 const int frame_size = s->audio.enc->frame_size;
737 int consumed_samples = 0;
738 while (tmp_frm->nb_samples + // NOLINT
739 (aud_frm->nb_samples - consumed_samples) >=
740 frame_size) {
741 const int needed_samples = frame_size - tmp_frm->nb_samples;
742 file_append_audio_frame(tmp_frm, aud_frm, consumed_samples,
743 needed_samples);
744 write_frame(s->format_ctx, &s->audio, tmp_frm, pkt);
745 s->audio.next_pts += frame_size;
746 consumed_samples += needed_samples;
747 tmp_frm->nb_samples = 0;
748 }
749
750 file_append_audio_frame(tmp_frm, aud_frm, consumed_samples,
751 aud_frm->nb_samples - consumed_samples);
752}
753
754static void
755write_video_frame(struct state_file *s, struct video_frame *vid_frm,
756 AVPacket *pkt)
757{
758 const long long vid_frm_time_ns =
759 (long long) (NS_IN_SEC / s->video_desc.fps);
760 AVFrame *frame =
761 s->video_conv == NULL
762 ? vid_frm->callbacks.dispose_udata
763 : to_lavc_vid_conv(s->video_conv, vid_frm->tiles[0].data);
764 bool dup = false;
765
766 // handle AV sync
767 if (s->audio.st != NULL) {
768 const time_ns_t audio_start =
770 s->audio.next_pts * NS_IN_SEC / s->audio.enc->sample_rate;
771 const time_ns_t video_start =
773 (long long) ((double) (s->video.next_pts * NS_IN_SEC) /
774 s->video_desc.fps);
775 if (s->video.next_frm_time != 0 &&
776 llabs(audio_start - video_start) > s->max_av_diff_ns) {
777 log_msg(
779 MOD_NAME "A-V desync %f sec, video frame %s...\n",
780 (double) (audio_start - video_start) /
782 video_start < audio_start ? "dropped" : "dupped");
783 if (video_start < audio_start) {
784 return; // drop frame
785 }
786 dup = true;
787 }
788 }
789
791 write_frame(s->format_ctx, &s->video, frame, pkt);
792 s->video.next_pts += 1;
793 s->video.next_frm_time = get_time_in_ns() + vid_frm_time_ns;
794
795 if (dup) {
796 dup = false;
797 goto write_frame;
798 }
799}
800
801static void *
802worker(void *arg)
803{
804 struct state_file *s = arg;
805
806 struct video_desc saved_vid_desc = { 0 };
807 struct audio_desc saved_aud_desc = { 0 };
808 struct video_frame *vid_frm = NULL;
809 AVFrame *aud_frm = NULL;
810 AVFrame *tmp_aud_frm = NULL;
811 AVPacket *pkt = av_packet_alloc();
812
813 while (1) {
814 pthread_mutex_lock(&s->lock);
815 while (s->audio.aud_frm == NULL && s->video.vid_frm == NULL &&
816 !s->should_exit) {
817 pthread_cond_wait(&s->cv, &s->lock);
818 }
819 if (s->should_exit) {
820 break;
821 }
822 if (s->video.vid_frm) {
823 vf_free(vid_frm);
824 vid_frm = s->video.vid_frm;
825 }
826 if (s->audio.aud_frm) {
827 av_frame_free(&aud_frm);
828 aud_frm = s->audio.aud_frm;
829 }
830 s->video.vid_frm = NULL;
831 s->audio.aud_frm = NULL;
832 pthread_mutex_unlock(&s->lock);
833
834 if (!s->initialized) {
835 if (!initialize(s, &saved_vid_desc, vid_frm,
836 &saved_aud_desc, aud_frm,
837 &tmp_aud_frm)) {
838 continue;
839 }
840 }
841
842 if (!check_reconf(&saved_vid_desc, vid_frm, &saved_aud_desc,
843 aud_frm)) {
844 error_msg(MOD_NAME "Reconfiguration not implemented. "
845 "Let us know if desired.\n");
846 continue;
847 }
848
849 if (aud_frm) {
850 write_audio_frame(s, aud_frm, tmp_aud_frm, pkt);
851 av_frame_free(&aud_frm);
852 }
853 if (vid_frm) {
854 write_video_frame(s, vid_frm, pkt);
855 vf_free(vid_frm);
856 vid_frm = NULL;
857 }
858 }
859 vf_free(vid_frm);
860 av_frame_free(&aud_frm);
861 if (tmp_aud_frm != NULL && tmp_aud_frm->nb_samples > 0) { // last frame
862 write_frame(s->format_ctx, &s->audio, tmp_aud_frm, pkt);
863 }
864 av_frame_free(&tmp_aud_frm);
865 av_packet_free(&pkt);
866
867 pthread_mutex_unlock(&s->lock);
868 return NULL;
869}
870
871static void
873{
874 struct state_file *s = state;
875
876 // store always as intereleaved integer samples
877 AVFrame *av_frm =
879 frame->data_len / frame->ch_count / frame->bps,
880 audio_bps_to_av_sample_fmt(frame->bps, false));
881 if (av_frm == NULL) {
882 return;
883 }
884 memcpy(av_frm->data[0], frame->data, frame->data_len);
885 pthread_mutex_lock(&s->lock);
886 if (s->audio.aud_frm != NULL) {
887 log_msg(LOG_LEVEL_WARNING, MOD_NAME "Audio frame dropped!\n");
888 av_frame_free(&s->audio.aud_frm);
889 }
890 s->audio.aud_frm = av_frm;
891 pthread_mutex_unlock(&s->lock);
892 pthread_cond_signal(&s->cv);
893}
894
895static bool
903
904static const void *
906{
907 static const struct video_display_info display_file_info = {
910 NULL, // _run
918 MOD_NAME,
919 };
920 return &display_file_info;
921};
922
#define UNUSED(x)
void ** state
int void * val
struct video_desc desc
int int int sample_rate
int int channels
struct video_frame * frame
const char * fmt
int void size_t * len
const char unsigned int flags
int void(** deleter)(void *))
int property
int quant_samples
void vf_free(struct video_frame *buf)
Frees video_frame structure.
struct video_desc video_desc_from_frame(const struct video_frame *frame)
Returns struct video_desc from video frame.
struct video_frame * vf_alloc_desc(struct video_desc desc)
Allocates video frame accordig given desc.
struct video_frame * vf_alloc_desc_data(struct video_desc desc)
This file contains definition of some common audio types.
@ AC_PCM
Definition types.h:53
void audio_start(struct state_audio *s)
Definition audio.cpp:463
int color_printf(const char *format,...)
#define TRED(x)
Definition color_out.h:75
#define TBOLD(x)
Definition color_out.h:70
void log_msg(int level, const char *format,...)
Definition debug.cpp:130
#define error_msg(...)
Definition debug.h:89
#define LOG_LEVEL_INFO
normal reporting
Definition debug.h:62
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
#define LOG_LEVEL_WARNING
less severe errors
Definition debug.h:60
#define MAX_PATH_SIZE
Definition fs.h:48
#define PUTF_DISCARD
#define VIDEO_DISPLAY_ABI_VERSION
#define DISPLAY_FLAG_AUDIO_ANY
@ DISPLAY_PROPERTY_CODECS
list of natively supported codecs - codec_t[]
@ DISPLAY_PROPERTY_AUDIO_FORMAT
int count
Definition hd-rum.c:44
void exit_uv(int status)
Definition host.cpp:625
#define INIT_NOERR
Definition host.h:73
enum AVSampleFormat audio_bps_to_av_sample_fmt(int bps, bool planar)
struct audio_desc audio_desc_from_av_frame(const AVFrame *frm)
void ug_set_av_logging()
Sets specified log level either given explicitly or from UG-wide log_level.
void usage()
Definition main.cpp:28
@ LIBRARY_CLASS_VIDEO_DISPLAY
Definition lib_common.h:79
#define REGISTER_MODULE_WITH_FUNC(name, func, lclass, abi)
Definition lib_common.h:150
#define MIN(a, b)
Definition macros.h:63
#define IS_KEY_PREFIX(tok, key)
expands to true value if <k> from tok in format <k>=<v> is prefix of key
Definition macros.h:138
int get_framerate_d(double fps)
Definition misc.cpp:190
int get_cpu_core_count(void)
Definition misc.cpp:218
int get_framerate_n(double fps)
Definition misc.cpp:184
const char * parent
Definition settings.cpp:198
const char * name
Definition settings.cpp:193
int sample_rate
Definition types.h:78
int ch_count
Definition types.h:79
int bps
Definition types.h:77
struct used to comunicate with audio capture/playback device
Definition types.h:99
Definition hd-rum.c:27
AVFrame * aud_frm
Definition file.c:84
long long int next_pts
Definition file.c:80
AVStream * st
Definition file.c:78
time_ns_t next_frm_time
Definition file.c:81
AVCodecContext * enc
Definition file.c:79
struct video_frame * vid_frm
Definition file.c:83
struct output_stream video
Definition file.c:92
struct output_stream audio
Definition file.c:91
struct to_lavc_vid_conv * video_conv
Definition file.c:94
char filename[MAX_PATH_SIZE]
Definition file.c:95
pthread_cond_t cv
Definition file.c:99
struct video_desc video_desc
Definition file.c:93
bool is_nut
Definition file.c:90
pthread_t thread_id
Definition file.c:97
time_ns_t max_av_diff_ns
max A/V diff in ns
Definition file.c:96
bool initialized
Definition file.c:100
bool should_exit
Definition file.c:101
pthread_mutex_t lock
Definition file.c:98
AVFormatContext * format_ctx
Definition file.c:89
char * data
Raw tile data. Pointer must be at least 4B aligned.
Definition types.h:287
codec_t color_spec
Definition types.h:182
double fps
Definition types.h:183
unsigned int width
width of every tile (!)
Definition types.h:179
unsigned int height
height of every tile (!)
Definition types.h:180
void(* data_deleter)(struct video_frame *)
Definition types.h:256
Struct video_frame represents a video frame and contains video description.
Definition types.h:305
struct tile tiles[FLEXIBLE_ARRAY_MEMBER]
Definition types.h:345
struct video_frame_callbacks callbacks
Definition types.h:324
void to_lavc_vid_conv_destroy(struct to_lavc_vid_conv **s_p)
int get_available_pix_fmts(codec_t in_codec, struct to_lavc_req_prop req_prop, enum AVPixelFormat fmts[AV_PIX_FMT_NB])
struct AVFrame * to_lavc_vid_conv(struct to_lavc_vid_conv *s, char *in_data)
struct to_lavc_vid_conv * to_lavc_vid_conv_init(codec_t in_pixfmt, int width, int height, enum AVPixelFormat out_pixfmt, int thread_count)
#define TO_LAVC_REQ_PROP_INIT
#define MS_IN_NS
Definition tv.h:85
long long time_ns_t
Definition tv.h:81
#define NS_IN_SEC
Definition tv.h:92
#define NS_IN_SEC_DBL
Definition tv.h:93
static time_ns_t get_time_in_ns()
Definition tv.h:101
This file contains some common data types.
codec_t
Definition types.h:65
@ R10k
RGB 10-bit packed - RGBX, big-endian (2 bit padding)
Definition types.h:73
@ VIDEO_CODEC_COUNT
count of known video codecs (including VIDEO_CODEC_NONE)
Definition types.h:108
@ R12L
RGB 12-bit packed, little-endian.
Definition types.h:74
@ v210
YCbCr 422 10-bit - xVY0U|xY2UY1|xUY3V|xY5VY4, little-endian (2 bit padding /'x'/),...
Definition types.h:75
char * wrap_paragraph(char *text)
Definition text.c:204
enum AVPixelFormat get_ug_to_av_pixfmt(codec_t ug_codec)
Definition utils.c:84
struct audio_desc audio_desc_from_frame(const struct audio_frame *frame)
Definition utils.cpp:196
bool audio_desc_eq(struct audio_desc a1, struct audio_desc a2)
Definition utils.cpp:189
void interleaved2noninterleaved_float(char **out_ch, const char *in, int in_bps, int in_len, int channel_count)
Definition utils.cpp:578
void interleaved2noninterleaved2(char **out_ch, const char *in, int bps, int in_len, int channel_count)
Definition utils.cpp:566
codec_t ug_codec
Definition v4l2_common.h:68
This is an umbrella header for video functions.
#define MOD_NAME
Definition file.c:95
static struct @76 codecs[]
static bool display_file_reconfigure_audio(void *state, int quant_samples, int channels, int sample_rate)
Definition file.c:896
static void delete_frame(struct video_frame *frame)
Definition file.c:245
#define DEFAULT_FILENAME
Definition file.c:69
static void file_append_audio_frame(AVFrame *dst, const AVFrame *src, int skip_in_samples, int nb_samples)
append nb_samples to from src to dst, skipping skip_in_samples from input
Definition file.c:677
static void write_audio_frame(struct state_file *s, AVFrame *aud_frm, AVFrame *tmp_frm, AVPacket *pkt)
Definition file.c:722
static void display_file_done(void *state)
Definition file.c:118
static void write_frame(AVFormatContext *format_ctx, struct output_stream *ost, AVFrame *frame, AVPacket *pkt)
Definition file.c:622
static bool configure_audio(struct state_file *s, struct audio_desc aud_desc, AVFrame **tmp_frame)
Definition file.c:467
static void aud_frm_set_ch_layout(AVFrame *frm, int ch_count)
Definition file.c:415
#define DEFAULT_PIXEL_FORMAT
Definition file.c:70
static const void * display_file_info_get()
Definition file.c:905
static enum AVPixelFormat file_get_pix_fmt(bool is_nut, codec_t ug_codec)
Definition file.c:252
static void write_video_frame(struct state_file *s, struct video_frame *vid_frm, AVPacket *pkt)
Definition file.c:755
static bool avp_is_in_set(enum AVPixelFormat needle, int nmembers, const enum AVPixelFormat *haystick)
Definition file.c:324
static void * worker(void *arg)
Definition file.c:802
static bool check_reconf(struct video_desc *saved_vid_desc, const struct video_frame *vid_frm, struct audio_desc *saved_aud_desc, const AVFrame *aud_frm)
Definition file.c:654
static void display_file_probe(struct device_info **available_cards, int *count, void(**deleter)(void *))
Definition file.c:107
@ DEFAULT_MAX_AV_DIFF_NS
Definition file.c:74
static bool display_file_reconfigure(void *state, struct video_desc desc)
Definition file.c:390
static struct video_frame * display_file_getf(void *state)
Definition file.c:270
static void * display_file_init(struct module *parent, const char *fmt, unsigned int flags)
Definition file.c:188
static enum AVSampleFormat select_sample_format(const enum AVSampleFormat *fmts)
Definition file.c:448
static bool display_file_putf(void *state, struct video_frame *frame, long long timeout_ns)
Definition file.c:297
static bool parse_fmt(struct state_file *s, char *fmt)
Definition file.c:165
static void aud_ctx_set_ch_layout(struct AVCodecContext *ctx, int ch_count, bool raw)
Definition file.c:399
static bool configure_video(struct state_file *s, struct video_desc vid_desc)
Definition file.c:531
static void display_file_put_audio_frame(void *state, const struct audio_frame *frame)
Definition file.c:872
static bool initialize(struct state_file *s, struct video_desc *saved_vid_desc, const struct video_frame *vid_frm, struct audio_desc *saved_aud_desc, const AVFrame *aud_frm, AVFrame **tmp_aud_frame)
Definition file.c:578
static bool display_file_get_property(void *state, int property, void *val, size_t *len)
Definition file.c:336
static AVFrame * alloc_audio_frame(struct audio_desc desc, int nb_samples, enum AVSampleFormat fmt)
Definition file.c:426
bool video_desc_eq(struct video_desc a, struct video_desc b)
Compares two video descriptions.