Unified · Split
| @@ -34,6 +34,7 @@ version <next>: · 33 unmodified lines | ||
| 34 | 34 | - floodfill video filter |
| 35 | 35 | - pseudocolor video filter |
| 36 | 36 | - raw G.726 muxer and demuxer, left- and right-justified |
| 37 | +- NewTek NDI input/output device | |
| 37 | 38 | |
| 38 | 39 | version 3.3: |
| 39 | 40 | - CrystalHD decoder moved to new decode API |
| @@ -277,6 +277,7 @@ External library support: · 276 unmodified lines | ||
| 277 | 277 | --enable-libzvbi enable teletext support via libzvbi [no] |
| 278 | 278 | --disable-lzma disable lzma [autodetect] |
| 279 | 279 | --enable-decklink enable Blackmagic DeckLink I/O support [no] |
| 280 | + --enable-libndi_newtek enable Newteck NDI I/O support [no] | |
| 280 | 281 | --enable-mediacodec enable Android MediaCodec support [no] |
| 281 | 282 | --enable-libmysofa enable libmysofa, needed for sofalizer filter [no] |
| 282 | 283 | --enable-openal enable OpenAL 1.1 capture support [no] |
| @@ -1508,6 +1509,7 @@ EXTERNAL_LIBRARY_GPL_LIST=" · 1225 unmodified lines | ||
| 1508 | 1509 | |
| 1509 | 1510 | EXTERNAL_LIBRARY_NONFREE_LIST=" |
| 1510 | 1511 | decklink |
| 1512 | + libndi_newtek | |
| 1511 | 1513 | libfdk_aac |
| 1512 | 1514 | openssl |
| 1513 | 1515 | " |
| @@ -3014,6 +3016,10 @@ decklink_indev_deps="decklink threads" · 1500 unmodified lines | ||
| 3014 | 3016 | decklink_indev_extralibs="-lstdc++" |
| 3015 | 3017 | decklink_outdev_deps="decklink threads" |
| 3016 | 3018 | decklink_outdev_extralibs="-lstdc++" |
| 3019 | +libndi_newtek_indev_deps="libndi_newtek" | |
| 3020 | +libndi_newtek_indev_extralibs="-lndi" | |
| 3021 | +libndi_newtek_outdev_deps="libndi_newtek" | |
| 3022 | +libndi_newtek_outdev_extralibs="-lndi" | |
| 3017 | 3023 | dshow_indev_deps="IBaseFilter" |
| 3018 | 3024 | dshow_indev_extralibs="-lpsapi -lole32 -lstrmiids -luuid -loleaut32 -lshlwapi" |
| 3019 | 3025 | dv1394_indev_deps="dv1394" |
| @@ -5567,6 +5573,8 @@ avisynth_demuxer_extralibs='$ldl' · 2547 unmodified lines | ||
| 5567 | 5573 | cuda_extralibs='$ldl' |
| 5568 | 5574 | decklink_outdev_extralibs="$decklink_outdev_extralibs $ldl" |
| 5569 | 5575 | decklink_indev_extralibs="$decklink_indev_extralibs $ldl" |
| 5576 | +libndi_newtek_outdev_extralibs="$libndi_newtek_outdev_extralibs $ldl" | |
| 5577 | +libndi_newtek_indev_extralibs="$libndi_newtek_indev_extralibs $ldl" | |
| 5570 | 5578 | frei0r_filter_extralibs='$ldl' |
| 5571 | 5579 | frei0r_src_filter_extralibs='$ldl' |
| 5572 | 5580 | ladspa_filter_extralibs='$ldl' |
| @@ -5825,6 +5833,7 @@ enabled coreimage_filter && { check_header_objcc QuartzCore/CoreImage.h || disa · 252 unmodified lines | ||
| 5825 | 5833 | enabled coreimagesrc_filter && { check_header_objcc QuartzCore/CoreImage.h || disable coreimagesrc_filter; } |
| 5826 | 5834 | enabled decklink && { { check_header DeckLinkAPI.h || die "ERROR: DeckLinkAPI.h header not found"; } && |
| 5827 | 5835 | { check_cpp_condition DeckLinkAPIVersion.h "BLACKMAGIC_DECKLINK_API_VERSION >= 0x0a060100" || die "ERROR: Decklink API version must be >= 10.6.1."; } } |
| 5836 | +enabled libndi_newtek && { check_header Processing.NDI.Lib.h || die "ERROR: Processing.NDI.Lib.h header not found"; } | |
| 5828 | 5837 | enabled frei0r && { check_header frei0r.h || die "ERROR: frei0r.h header not found"; } |
| 5829 | 5838 | enabled gmp && require gmp gmp.h mpz_export -lgmp |
| 5830 | 5839 | enabled gnutls && require_pkg_config gnutls gnutls/gnutls.h gnutls_global_init |
| @@ -332,6 +332,54 @@ ffmpeg -channels 16 -format_code Hi50 -f decklink -i 'UltraStudio Mini Recorder' · 331 unmodified lines | ||
| 332 | 332 | |
| 333 | 333 | @end itemize |
| 334 | 334 | |
| 335 | +@section libndi_newtek | |
| 336 | + | |
| 337 | +The libndi_newtek input device provides capture capabilities for using NDI (Network | |
| 338 | +Device Interface, standard created by NewTek). | |
| 339 | + | |
| 340 | +Input filename is a NDI source name that could be found by sending -find_sources 1 | |
| 341 | +to command line - it has no specific syntax but human-readable formatted. | |
| 342 | + | |
| 343 | +To enable this input device, you need the NDI SDK and you | |
| 344 | +need to configure with the appropriate @code{--extra-cflags} | |
| 345 | +and @code{--extra-ldflags}. | |
| 346 | + | |
| 347 | +@subsection Options | |
| 348 | + | |
| 349 | +@table @option | |
| 350 | + | |
| 351 | +@item find_sources | |
| 352 | +If set to @option{true}, print a list of found/available NDI sources and exit. | |
| 353 | +Defaults to @option{false}. | |
| 354 | + | |
| 355 | +@item wait_sources | |
| 356 | +Override time to wait until the number of online sources have changed. | |
| 357 | +Defaults to @option{0.5}. | |
| 358 | + | |
| 359 | +@item allow_video_fields | |
| 360 | +When this flag is @option{false}, all video that you receive will be progressive. | |
| 361 | +Defaults to @option{true}. | |
| 362 | + | |
| 363 | +@end table | |
| 364 | + | |
| 365 | +@subsection Examples | |
| 366 | + | |
| 367 | +@itemize | |
| 368 | + | |
| 369 | +@item | |
| 370 | +List input devices: | |
| 371 | +@example | |
| 372 | +ffmpeg -f libndi_newtek -find_sources 1 -i dummy | |
| 373 | +@end example | |
| 374 | + | |
| 375 | +@item | |
| 376 | +Restream to NDI: | |
| 377 | +@example | |
| 378 | +ffmpeg -f libndi_newtek -i "DEV-5.INTERNAL.M1STEREO.TV (NDI_SOURCE_NAME_1)" -f libndi_newtek -y NDI_SOURCE_NAME_2 | |
| 379 | +@end example | |
| 380 | + | |
| 381 | +@end itemize | |
| 382 | + | |
| 335 | 383 | @section dshow |
| 336 | 384 | |
| 337 | 385 | Windows DirectShow input device. |
| @@ -182,6 +182,51 @@ ffmpeg -i test.avi -f decklink -pix_fmt uyvy422 -s 720x486 -r 24000/1001 'DeckLi · 181 unmodified lines | ||
| 182 | 182 | |
| 183 | 183 | @end itemize |
| 184 | 184 | |
| 185 | +@section libndi_newtek | |
| 186 | + | |
| 187 | +The libndi_newtek output device provides playback capabilities for using NDI (Network | |
| 188 | +Device Interface, standard created by NewTek). | |
| 189 | + | |
| 190 | +Output filename is a NDI name. | |
| 191 | + | |
| 192 | +To enable this output device, you need the NDI SDK and you | |
| 193 | +need to configure with the appropriate @code{--extra-cflags} | |
| 194 | +and @code{--extra-ldflags}. | |
| 195 | + | |
| 196 | +NDI uses uyvy422 pixel format natively, but also supports bgra, bgr0, rgba and | |
| 197 | +rgb0. | |
| 198 | + | |
| 199 | +@subsection Options | |
| 200 | + | |
| 201 | +@table @option | |
| 202 | + | |
| 203 | +@item reference_level | |
| 204 | +The audio reference level in dB. This specifies how many dB above the | |
| 205 | +reference level (+4dBU) is the full range of 16 bit audio. | |
| 206 | +Defaults to @option{0}. | |
| 207 | + | |
| 208 | +@item clock_video | |
| 209 | +These specify whether video "clock" themselves. | |
| 210 | +Defaults to @option{false}. | |
| 211 | + | |
| 212 | +@item clock_audio | |
| 213 | +These specify whether audio "clock" themselves. | |
| 214 | +Defaults to @option{false}. | |
| 215 | + | |
| 216 | +@end table | |
| 217 | + | |
| 218 | +@subsection Examples | |
| 219 | + | |
| 220 | +@itemize | |
| 221 | + | |
| 222 | +@item | |
| 223 | +Play video clip: | |
| 224 | +@example | |
| 225 | +ffmpeg -i "udp://@@239.1.1.1:10480?fifo_size=1000000&overrun_nonfatal=1" -vf "scale=720:576,fps=fps=25,setdar=dar=16/9,format=pix_fmts=uyvy422" -f libndi_newtek NEW_NDI1 | |
| 226 | +@end example | |
| 227 | + | |
| 228 | +@end itemize | |
| 229 | + | |
| 185 | 230 | @section fbdev |
| 186 | 231 | |
| 187 | 232 | Linux framebuffer output device. |
| @@ -19,6 +19,8 @@ OBJS-$(CONFIG_BKTR_INDEV) += bktr.o · 18 unmodified lines | ||
| 19 | 19 | OBJS-$(CONFIG_CACA_OUTDEV) += caca.o |
| 20 | 20 | OBJS-$(CONFIG_DECKLINK_OUTDEV) += decklink_enc.o decklink_enc_c.o decklink_common.o |
| 21 | 21 | OBJS-$(CONFIG_DECKLINK_INDEV) += decklink_dec.o decklink_dec_c.o decklink_common.o |
| 22 | +OBJS-$(CONFIG_LIBNDI_NEWTEK_OUTDEV) += libndi_newtek_enc.o | |
| 23 | +OBJS-$(CONFIG_LIBNDI_NEWTEK_INDEV) += libndi_newtek_dec.o | |
| 22 | 24 | OBJS-$(CONFIG_DSHOW_INDEV) += dshow_crossbar.o dshow.o dshow_enummediatypes.o \ |
| 23 | 25 | dshow_enumpins.o dshow_filter.o \ |
| 24 | 26 | dshow_pin.o dshow_common.o |
| @@ -60,6 +62,8 @@ SLIBOBJS-$(HAVE_GNU_WINDRES) += avdeviceres.o · 35 unmodified lines | ||
| 60 | 62 | SKIPHEADERS += decklink_common.h |
| 61 | 63 | SKIPHEADERS-$(CONFIG_DECKLINK) += decklink_enc.h decklink_dec.h \ |
| 62 | 64 | decklink_common_c.h |
| 65 | +SKIPHEADERS-$(CONFIG_LIBNDI_NEWTEK_INDEV) += libndi_newtek_common.h | |
| 66 | +SKIPHEADERS-$(CONFIG_LIBNDI_NEWTEK_OUTDEV) += libndi_newtek_common.h | |
| 63 | 67 | SKIPHEADERS-$(CONFIG_DSHOW_INDEV) += dshow_capture.h |
| 64 | 68 | SKIPHEADERS-$(CONFIG_FBDEV_INDEV) += fbdev_common.h |
| 65 | 69 | SKIPHEADERS-$(CONFIG_FBDEV_OUTDEV) += fbdev_common.h |
| @@ -46,6 +46,7 @@ static void register_all(void) · 45 unmodified lines | ||
| 46 | 46 | REGISTER_INDEV (BKTR, bktr); |
| 47 | 47 | REGISTER_OUTDEV (CACA, caca); |
| 48 | 48 | REGISTER_INOUTDEV(DECKLINK, decklink); |
| 49 | + REGISTER_INOUTDEV(LIBNDI_NEWTEK, libndi_newtek); | |
| 49 | 50 | REGISTER_INDEV (DSHOW, dshow); |
| 50 | 51 | REGISTER_INDEV (DV1394, dv1394); |
| 51 | 52 | REGISTER_INOUTDEV(FBDEV, fbdev); |
| @@ -0,0 +1,30 @@ | ||
| 1 | +/* | |
| 2 | + * NewTek NDI common code | |
| 3 | + * Copyright (c) 2017 Maksym Veremeyenko | |
| 4 | + * | |
| 5 | + * This file is part of FFmpeg. | |
| 6 | + * | |
| 7 | + * FFmpeg is free software; you can redistribute it and/or | |
| 8 | + * modify it under the terms of the GNU Lesser General Public | |
| 9 | + * License as published by the Free Software Foundation; either | |
| 10 | + * version 2.1 of the License, or (at your option) any later version. | |
| 11 | + * | |
| 12 | + * FFmpeg is distributed in the hope that it will be useful, | |
| 13 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 14 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 15 | + * Lesser General Public License for more details. | |
| 16 | + * | |
| 17 | + * You should have received a copy of the GNU Lesser General Public | |
| 18 | + * License along with FFmpeg; if not, write to the Free Software | |
| 19 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| 20 | + */ | |
| 21 | + | |
| 22 | +#ifndef AVDEVICE_LIBNDI_NEWTEK_COMMON_H | |
| 23 | +#define AVDEVICE_LIBNDI_NEWTEK_COMMON_H | |
| 24 | + | |
| 25 | +#include <Processing.NDI.Lib.h> | |
| 26 | + | |
| 27 | +#define NDI_TIME_BASE 10000000 | |
| 28 | +#define NDI_TIME_BASE_Q (AVRational){1, NDI_TIME_BASE} | |
| 29 | + | |
| 30 | +#endif | |
| @@ -0,0 +1,340 @@ | ||
| 1 | +/* | |
| 2 | + * Newtek NDI input | |
| 3 | + * Copyright (c) 2017 Maksym Veremeyenko | |
| 4 | + * | |
| 5 | + * This file is part of FFmpeg. | |
| 6 | + * | |
| 7 | + * FFmpeg is free software; you can redistribute it and/or | |
| 8 | + * modify it under the terms of the GNU Lesser General Public | |
| 9 | + * License as published by the Free Software Foundation; either | |
| 10 | + * version 2.1 of the License, or (at your option) any later version. | |
| 11 | + * | |
| 12 | + * FFmpeg is distributed in the hope that it will be useful, | |
| 13 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 14 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 15 | + * Lesser General Public License for more details. | |
| 16 | + * | |
| 17 | + * You should have received a copy of the GNU Lesser General Public | |
| 18 | + * License along with FFmpeg; if not, write to the Free Software | |
| 19 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| 20 | + */ | |
| 21 | + | |
| 22 | +#include "libavformat/avformat.h" | |
| 23 | +#include "libavformat/internal.h" | |
| 24 | +#include "libavutil/opt.h" | |
| 25 | +#include "libavutil/imgutils.h" | |
| 26 | + | |
| 27 | +#include "libndi_newtek_common.h" | |
| 28 | + | |
| 29 | +struct NDIContext { | |
| 30 | + const AVClass *cclass; | |
| 31 | + | |
| 32 | + /* Options */ | |
| 33 | + int find_sources; | |
| 34 | + int64_t wait_sources; | |
| 35 | + int allow_video_fields; | |
| 36 | + | |
| 37 | + /* Runtime */ | |
| 38 | + NDIlib_recv_create_t *recv; | |
| 39 | + NDIlib_find_instance_t ndi_find; | |
| 40 | + | |
| 41 | + /* Streams */ | |
| 42 | + AVStream *video_st, *audio_st; | |
| 43 | +}; | |
| 44 | + | |
| 45 | +static int ndi_set_video_packet(AVFormatContext *avctx, NDIlib_video_frame_t *v, AVPacket *pkt) | |
| 46 | +{ | |
| 47 | + int ret; | |
| 48 | + struct NDIContext *ctx = avctx->priv_data; | |
| 49 | + | |
| 50 | + ret = av_new_packet(pkt, v->yres * v->line_stride_in_bytes); | |
| 51 | + if (ret < 0) | |
| 52 | + return ret; | |
| 53 | + | |
| 54 | + pkt->dts = pkt->pts = av_rescale_q(v->timecode, NDI_TIME_BASE_Q, ctx->video_st->time_base); | |
| 55 | + pkt->duration = av_rescale_q(1, (AVRational){v->frame_rate_D, v->frame_rate_N}, ctx->video_st->time_base); | |
| 56 | + | |
| 57 | + av_log(avctx, AV_LOG_DEBUG, "%s: pkt->dts = pkt->pts = %"PRId64", duration=%"PRId64", timecode=%"PRId64"\n", | |
| 58 | + __func__, pkt->dts, pkt->duration, v->timecode); | |
| 59 | + | |
| 60 | + pkt->flags |= AV_PKT_FLAG_KEY; | |
| 61 | + pkt->stream_index = ctx->video_st->index; | |
| 62 | + | |
| 63 | + memcpy(pkt->data, v->p_data, pkt->size); | |
| 64 | + | |
| 65 | + return 0; | |
| 66 | +} | |
| 67 | + | |
| 68 | +static int ndi_set_audio_packet(AVFormatContext *avctx, NDIlib_audio_frame_t *a, AVPacket *pkt) | |
| 69 | +{ | |
| 70 | + int ret; | |
| 71 | + struct NDIContext *ctx = avctx->priv_data; | |
| 72 | + | |
| 73 | + NDIlib_audio_frame_interleaved_16s_t dst; | |
| 74 | + | |
| 75 | + ret = av_new_packet(pkt, 2 * a->no_samples * a->no_channels); | |
| 76 | + if (ret < 0) | |
| 77 | + return ret; | |
| 78 | + | |
| 79 | + pkt->dts = pkt->pts = av_rescale_q(a->timecode, NDI_TIME_BASE_Q, ctx->audio_st->time_base); | |
| 80 | + pkt->duration = av_rescale_q(1, (AVRational){a->no_samples, a->sample_rate}, ctx->audio_st->time_base); | |
| 81 | + | |
| 82 | + av_log(avctx, AV_LOG_DEBUG, "%s: pkt->dts = pkt->pts = %"PRId64", duration=%"PRId64", timecode=%"PRId64"\n", | |
| 83 | + __func__, pkt->dts, pkt->duration, a->timecode); | |
| 84 | + | |
| 85 | + pkt->flags |= AV_PKT_FLAG_KEY; | |
| 86 | + pkt->stream_index = ctx->audio_st->index; | |
| 87 | + | |
| 88 | + dst.reference_level = 0; | |
| 89 | + dst.p_data = (short *)pkt->data; | |
| 90 | + NDIlib_util_audio_to_interleaved_16s(a, &dst); | |
| 91 | + | |
| 92 | + return 0; | |
| 93 | +} | |
| 94 | + | |
| 95 | +static int ndi_find_sources(AVFormatContext *avctx, const char *name, NDIlib_source_t *source_to_connect_to) | |
| 96 | +{ | |
| 97 | + int j = AVERROR(ENODEV); | |
| 98 | + unsigned int n, i; | |
| 99 | + struct NDIContext *ctx = avctx->priv_data; | |
| 100 | + const NDIlib_source_t *ndi_srcs = NULL; | |
| 101 | + const NDIlib_find_create_t find_create_desc = { .show_local_sources = true, | |
| 102 | + .p_groups = NULL, .p_extra_ips = NULL }; | |
| 103 | + | |
| 104 | + if (!ctx->ndi_find) | |
| 105 | + ctx->ndi_find = NDIlib_find_create2(&find_create_desc); | |
| 106 | + if (!ctx->ndi_find) { | |
| 107 | + av_log(avctx, AV_LOG_ERROR, "NDIlib_find_create failed.\n"); | |
| 108 | + return AVERROR(EIO); | |
| 109 | + } | |
| 110 | + | |
| 111 | + while (1) | |
| 112 | + { | |
| 113 | + int f, t = ctx->wait_sources / 1000; | |
| 114 | + av_log(avctx, AV_LOG_DEBUG, "Waiting for sources %d miliseconds\n", t); | |
| 115 | + f = NDIlib_find_wait_for_sources(ctx->ndi_find, t); | |
| 116 | + av_log(avctx, AV_LOG_DEBUG, "NDIlib_find_wait_for_sources returns %d\n", f); | |
| 117 | + if (!f) | |
| 118 | + break; | |
| 119 | + }; | |
| 120 | + | |
| 121 | + ndi_srcs = NDIlib_find_get_current_sources(ctx->ndi_find, &n); | |
| 122 | + | |
| 123 | + if (ctx->find_sources) | |
| 124 | + av_log(avctx, AV_LOG_INFO, "Found %d NDI sources:\n", n); | |
| 125 | + | |
| 126 | + for (i = 0; i < n; i++) { | |
| 127 | + if (ctx->find_sources) | |
| 128 | + av_log(avctx, AV_LOG_INFO, "\t'%s'\t'%s'\n", ndi_srcs[i].p_ndi_name, ndi_srcs[i].p_ip_address); | |
| 129 | + | |
| 130 | + if (!strcmp(name, ndi_srcs[i].p_ndi_name)) { | |
| 131 | + *source_to_connect_to = ndi_srcs[i]; | |
| 132 | + j = i; | |
| 133 | + } | |
| 134 | + } | |
| 135 | + | |
| 136 | + return j; | |
| 137 | +} | |
| 138 | + | |
| 139 | +static int ndi_read_header(AVFormatContext *avctx) | |
| 140 | +{ | |
| 141 | + int ret; | |
| 142 | + NDIlib_recv_create_t recv_create_desc; | |
| 143 | + const NDIlib_tally_t tally_state = { .on_program = true, .on_preview = false }; | |
| 144 | + struct NDIContext *ctx = avctx->priv_data; | |
| 145 | + | |
| 146 | + if (!NDIlib_initialize()) { | |
| 147 | + av_log(avctx, AV_LOG_ERROR, "NDIlib_initialize failed.\n"); | |
| 148 | + return AVERROR_EXTERNAL; | |
| 149 | + } | |
| 150 | + | |
| 151 | + /* Find available sources. */ | |
| 152 | + ret = ndi_find_sources(avctx, avctx->filename, &recv_create_desc.source_to_connect_to); | |
| 153 | + if (ctx->find_sources) { | |
| 154 | + return AVERROR_EXIT; | |
| 155 | + } | |
| 156 | + if (ret < 0) | |
| 157 | + return ret; | |
| 158 | + | |
| 159 | + /* Create receiver description */ | |
| 160 | + recv_create_desc.color_format = NDIlib_recv_color_format_e_UYVY_RGBA; | |
| 161 | + recv_create_desc.bandwidth = NDIlib_recv_bandwidth_highest; | |
| 162 | + recv_create_desc.allow_video_fields = ctx->allow_video_fields; | |
| 163 | + | |
| 164 | + /* Create the receiver */ | |
| 165 | + ctx->recv = NDIlib_recv_create(&recv_create_desc); | |
| 166 | + if (!ctx->recv) { | |
| 167 | + av_log(avctx, AV_LOG_ERROR, "NDIlib_recv_create2 failed.\n"); | |
| 168 | + return AVERROR(EIO); | |
| 169 | + } | |
| 170 | + | |
| 171 | + /* Set tally */ | |
| 172 | + NDIlib_recv_set_tally(ctx->recv, &tally_state); | |
| 173 | + | |
| 174 | + avctx->ctx_flags |= AVFMTCTX_NOHEADER; | |
| 175 | + | |
| 176 | + return 0; | |
| 177 | +} | |
| 178 | + | |
| 179 | +static int ndi_create_video_stream(AVFormatContext *avctx, NDIlib_video_frame_t *v) | |
| 180 | +{ | |
| 181 | + AVStream *st; | |
| 182 | + AVRational tmp; | |
| 183 | + struct NDIContext *ctx = avctx->priv_data; | |
| 184 | + | |
| 185 | + st = avformat_new_stream(avctx, NULL); | |
| 186 | + if (!st) { | |
| 187 | + av_log(avctx, AV_LOG_ERROR, "Cannot add video stream\n"); | |
| 188 | + return AVERROR(ENOMEM); | |
| 189 | + } | |
| 190 | + | |
| 191 | + st->time_base = NDI_TIME_BASE_Q; | |
| 192 | + av_stream_set_r_frame_rate(st, av_make_q(v->frame_rate_N, v->frame_rate_D)); | |
| 193 | + | |
| 194 | + tmp = av_mul_q(av_d2q(v->picture_aspect_ratio, INT_MAX), (AVRational){v->yres, v->xres}); | |
| 195 | + av_reduce(&st->sample_aspect_ratio.num, &st->sample_aspect_ratio.den, tmp.num, tmp.den, 1000); | |
| 196 | + st->codecpar->sample_aspect_ratio = st->sample_aspect_ratio; | |
| 197 | + | |
| 198 | + st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; | |
| 199 | + st->codecpar->width = v->xres; | |
| 200 | + st->codecpar->height = v->yres; | |
| 201 | + st->codecpar->codec_id = AV_CODEC_ID_RAWVIDEO; | |
| 202 | + st->codecpar->bit_rate = av_rescale(v->xres * v->yres * 16, v->frame_rate_N, v->frame_rate_D); | |
| 203 | + st->codecpar->field_order = v->frame_format_type == NDIlib_frame_format_type_progressive | |
| 204 | + ? AV_FIELD_PROGRESSIVE : AV_FIELD_TT; | |
| 205 | + | |
| 206 | + if (NDIlib_FourCC_type_UYVY == v->FourCC || NDIlib_FourCC_type_UYVA == v->FourCC) { | |
| 207 | + st->codecpar->format = AV_PIX_FMT_UYVY422; | |
| 208 | + st->codecpar->codec_tag = MKTAG('U', 'Y', 'V', 'Y'); | |
| 209 | + if (NDIlib_FourCC_type_UYVA == v->FourCC) | |
| 210 | + av_log(avctx, AV_LOG_WARNING, "Alpha channel ignored\n"); | |
| 211 | + } else if (NDIlib_FourCC_type_BGRA == v->FourCC) { | |
| 212 | + st->codecpar->format = AV_PIX_FMT_BGRA; | |
| 213 | + st->codecpar->codec_tag = MKTAG('B', 'G', 'R', 'A'); | |
| 214 | + } else if (NDIlib_FourCC_type_BGRX == v->FourCC) { | |
| 215 | + st->codecpar->format = AV_PIX_FMT_BGR0; | |
| 216 | + st->codecpar->codec_tag = MKTAG('B', 'G', 'R', '0'); | |
| 217 | + } else if (NDIlib_FourCC_type_RGBA == v->FourCC) { | |
| 218 | + st->codecpar->format = AV_PIX_FMT_RGBA; | |
| 219 | + st->codecpar->codec_tag = MKTAG('R', 'G', 'B', 'A'); | |
| 220 | + } else if (NDIlib_FourCC_type_RGBX == v->FourCC) { | |
| 221 | + st->codecpar->format = AV_PIX_FMT_RGB0; | |
| 222 | + st->codecpar->codec_tag = MKTAG('R', 'G', 'B', '0'); | |
| 223 | + } else { | |
| 224 | + av_log(avctx, AV_LOG_ERROR, "Unsupported video stream format, v->FourCC=%d\n", v->FourCC); | |
| 225 | + return AVERROR(EINVAL); | |
| 226 | + } | |
| 227 | + | |
| 228 | + avpriv_set_pts_info(st, 64, 1, NDI_TIME_BASE); | |
| 229 | + | |
| 230 | + ctx->video_st = st; | |
| 231 | + | |
| 232 | + return 0; | |
| 233 | +} | |
| 234 | + | |
| 235 | +static int ndi_create_audio_stream(AVFormatContext *avctx, NDIlib_audio_frame_t *a) | |
| 236 | +{ | |
| 237 | + AVStream *st; | |
| 238 | + struct NDIContext *ctx = avctx->priv_data; | |
| 239 | + | |
| 240 | + st = avformat_new_stream(avctx, NULL); | |
| 241 | + if (!st) { | |
| 242 | + av_log(avctx, AV_LOG_ERROR, "Cannot add audio stream\n"); | |
| 243 | + return AVERROR(ENOMEM); | |
| 244 | + } | |
| 245 | + | |
| 246 | + st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; | |
| 247 | + st->codecpar->codec_id = AV_CODEC_ID_PCM_S16LE; | |
| 248 | + st->codecpar->sample_rate = a->sample_rate; | |
| 249 | + st->codecpar->channels = a->no_channels; | |
| 250 | + | |
| 251 | + avpriv_set_pts_info(st, 64, 1, NDI_TIME_BASE); | |
| 252 | + | |
| 253 | + ctx->audio_st = st; | |
| 254 | + | |
| 255 | + return 0; | |
| 256 | +} | |
| 257 | + | |
| 258 | +static int ndi_read_packet(AVFormatContext *avctx, AVPacket *pkt) | |
| 259 | +{ | |
| 260 | + int ret = 0; | |
| 261 | + struct NDIContext *ctx = avctx->priv_data; | |
| 262 | + | |
| 263 | + while (!ret) { | |
| 264 | + NDIlib_video_frame_t v; | |
| 265 | + NDIlib_audio_frame_t a; | |
| 266 | + NDIlib_metadata_frame_t m; | |
| 267 | + NDIlib_frame_type_e t; | |
| 268 | + | |
| 269 | + av_log(avctx, AV_LOG_DEBUG, "NDIlib_recv_capture...\n"); | |
| 270 | + t = NDIlib_recv_capture(ctx->recv, &v, &a, &m, 40); | |
| 271 | + av_log(avctx, AV_LOG_DEBUG, "NDIlib_recv_capture=%d\n", t); | |
| 272 | + | |
| 273 | + if (t == NDIlib_frame_type_video) { | |
| 274 | + if (!ctx->video_st) | |
| 275 | + ret = ndi_create_video_stream(avctx, &v); | |
| 276 | + if (!ret) | |
| 277 | + ret = ndi_set_video_packet(avctx, &v, pkt); | |
| 278 | + NDIlib_recv_free_video(ctx->recv, &v); | |
| 279 | + break; | |
| 280 | + } | |
| 281 | + else if (t == NDIlib_frame_type_audio) { | |
| 282 | + if (!ctx->audio_st) | |
| 283 | + ret = ndi_create_audio_stream(avctx, &a); | |
| 284 | + if (!ret) | |
| 285 | + ret = ndi_set_audio_packet(avctx, &a, pkt); | |
| 286 | + NDIlib_recv_free_audio(ctx->recv, &a); | |
| 287 | + break; | |
| 288 | + } | |
| 289 | + else if (t == NDIlib_frame_type_metadata) | |
| 290 | + NDIlib_recv_free_metadata(ctx->recv, &m); | |
| 291 | + else if (t == NDIlib_frame_type_error){ | |
| 292 | + av_log(avctx, AV_LOG_ERROR, "NDIlib_recv_capture failed with error\n"); | |
| 293 | + ret = AVERROR(EIO); | |
| 294 | + } | |
| 295 | + }; | |
| 296 | + | |
| 297 | + return ret; | |
| 298 | +} | |
| 299 | + | |
| 300 | +static int ndi_read_close(AVFormatContext *avctx) | |
| 301 | +{ | |
| 302 | + struct NDIContext *ctx = (struct NDIContext *)avctx->priv_data; | |
| 303 | + | |
| 304 | + if (ctx->recv) | |
| 305 | + NDIlib_recv_destroy(ctx->recv); | |
| 306 | + | |
| 307 | + if (ctx->ndi_find) | |
| 308 | + NDIlib_find_destroy(ctx->ndi_find); | |
| 309 | + | |
| 310 | + return 0; | |
| 311 | +} | |
| 312 | + | |
| 313 | +#define OFFSET(x) offsetof(struct NDIContext, x) | |
| 314 | +#define DEC AV_OPT_FLAG_DECODING_PARAM | |
| 315 | + | |
| 316 | +static const AVOption options[] = { | |
| 317 | + { "find_sources", "Find available sources" , OFFSET(find_sources), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, DEC }, | |
| 318 | + { "wait_sources", "Time to wait until the number of online sources have changed" , OFFSET(wait_sources), AV_OPT_TYPE_DURATION, { .i64 = 1000000 }, 100000, 20000000, DEC }, | |
| 319 | + { "allow_video_fields", "When this flag is FALSE, all video that you receive will be progressive" , OFFSET(allow_video_fields), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, DEC }, | |
| 320 | + { NULL }, | |
| 321 | +}; | |
| 322 | + | |
| 323 | +static const AVClass libndi_newtek_demuxer_class = { | |
| 324 | + .class_name = "NDI demuxer", | |
| 325 | + .item_name = av_default_item_name, | |
| 326 | + .option = options, | |
| 327 | + .version = LIBAVUTIL_VERSION_INT, | |
| 328 | + .category = AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT, | |
| 329 | +}; | |
| 330 | + | |
| 331 | +AVInputFormat ff_libndi_newtek_demuxer = { | |
| 332 | + .name = "libndi_newtek", | |
| 333 | + .long_name = NULL_IF_CONFIG_SMALL("Network Device Interface (NDI) input using NewTek library"), | |
| 334 | + .flags = AVFMT_NOFILE, | |
| 335 | + .priv_class = &libndi_newtek_demuxer_class, | |
| 336 | + .priv_data_size = sizeof(struct NDIContext), | |
| 337 | + .read_header = ndi_read_header, | |
| 338 | + .read_packet = ndi_read_packet, | |
| 339 | + .read_close = ndi_read_close, | |
| 340 | +}; | |
| @@ -0,0 +1,299 @@ | ||
| 1 | +/* | |
| 2 | + * NewTek NDI output | |
| 3 | + * Copyright (c) 2017 Maksym Veremeyenko | |
| 4 | + * | |
| 5 | + * This file is part of FFmpeg. | |
| 6 | + * | |
| 7 | + * FFmpeg is free software; you can redistribute it and/or | |
| 8 | + * modify it under the terms of the GNU Lesser General Public | |
| 9 | + * License as published by the Free Software Foundation; either | |
| 10 | + * version 2.1 of the License, or (at your option) any later version. | |
| 11 | + * | |
| 12 | + * FFmpeg is distributed in the hope that it will be useful, | |
| 13 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 14 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 15 | + * Lesser General Public License for more details. | |
| 16 | + * | |
| 17 | + * You should have received a copy of the GNU Lesser General Public | |
| 18 | + * License along with FFmpeg; if not, write to the Free Software | |
| 19 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| 20 | + */ | |
| 21 | + | |
| 22 | +#include "libavformat/avformat.h" | |
| 23 | +#include "libavformat/internal.h" | |
| 24 | +#include "libavutil/opt.h" | |
| 25 | +#include "libavutil/imgutils.h" | |
| 26 | + | |
| 27 | +#include "libndi_newtek_common.h" | |
| 28 | + | |
| 29 | +struct NDIContext { | |
| 30 | + const AVClass *cclass; | |
| 31 | + | |
| 32 | + /* Options */ | |
| 33 | + int reference_level; | |
| 34 | + int clock_video, clock_audio; | |
| 35 | + | |
| 36 | + NDIlib_video_frame_t *video; | |
| 37 | + NDIlib_audio_frame_interleaved_16s_t *audio; | |
| 38 | + NDIlib_send_instance_t ndi_send; | |
| 39 | + AVFrame *last_avframe; | |
| 40 | +}; | |
| 41 | + | |
| 42 | +static int ndi_write_trailer(AVFormatContext *avctx) | |
| 43 | +{ | |
| 44 | + struct NDIContext *ctx = avctx->priv_data; | |
| 45 | + | |
| 46 | + if (ctx->ndi_send) { | |
| 47 | + NDIlib_send_destroy(ctx->ndi_send); | |
| 48 | + av_frame_free(&ctx->last_avframe); | |
| 49 | + } | |
| 50 | + | |
| 51 | + av_freep(&ctx->video); | |
| 52 | + av_freep(&ctx->audio); | |
| 53 | + | |
| 54 | + return 0; | |
| 55 | +} | |
| 56 | + | |
| 57 | +static int ndi_write_video_packet(AVFormatContext *avctx, AVStream *st, AVPacket *pkt) | |
| 58 | +{ | |
| 59 | + struct NDIContext *ctx = avctx->priv_data; | |
| 60 | + AVFrame *avframe, *tmp = (AVFrame *)pkt->data; | |
| 61 | + | |
| 62 | + if (tmp->format != AV_PIX_FMT_UYVY422 && tmp->format != AV_PIX_FMT_BGRA && | |
| 63 | + tmp->format != AV_PIX_FMT_BGR0 && tmp->format != AV_PIX_FMT_RGBA && | |
| 64 | + tmp->format != AV_PIX_FMT_RGB0) { | |
| 65 | + av_log(avctx, AV_LOG_ERROR, "Got a frame with invalid pixel format.\n"); | |
| 66 | + return AVERROR(EINVAL); | |
| 67 | + } | |
| 68 | + | |
| 69 | + if (tmp->linesize[0] < 0) { | |
| 70 | + av_log(avctx, AV_LOG_ERROR, "Got a frame with negative linesize.\n"); | |
| 71 | + return AVERROR(EINVAL); | |
| 72 | + } | |
| 73 | + | |
| 74 | + if (tmp->width != ctx->video->xres || | |
| 75 | + tmp->height != ctx->video->yres) { | |
| 76 | + av_log(avctx, AV_LOG_ERROR, "Got a frame with invalid dimension.\n"); | |
| 77 | + av_log(avctx, AV_LOG_ERROR, "tmp->width=%d, tmp->height=%d, ctx->video->xres=%d, ctx->video->yres=%d\n", | |
| 78 | + tmp->width, tmp->height, ctx->video->xres, ctx->video->yres); | |
| 79 | + return AVERROR(EINVAL); | |
| 80 | + } | |
| 81 | + | |
| 82 | + avframe = av_frame_clone(tmp); | |
| 83 | + if (!avframe) | |
| 84 | + return AVERROR(ENOMEM); | |
| 85 | + | |
| 86 | + ctx->video->timecode = av_rescale_q(pkt->pts, st->time_base, NDI_TIME_BASE_Q); | |
| 87 | + | |
| 88 | + ctx->video->line_stride_in_bytes = avframe->linesize[0]; | |
| 89 | + ctx->video->p_data = (void *)(avframe->data[0]); | |
| 90 | + | |
| 91 | + av_log(avctx, AV_LOG_DEBUG, "%s: pkt->pts=%"PRId64", timecode=%"PRId64", st->time_base=%d/%d\n", | |
| 92 | + __func__, pkt->pts, ctx->video->timecode, st->time_base.num, st->time_base.den); | |
| 93 | + | |
| 94 | + /* asynchronous for one frame, but will block if a second frame | |
| 95 | + is given before the first one has been sent */ | |
| 96 | + NDIlib_send_send_video_async(ctx->ndi_send, ctx->video); | |
| 97 | + | |
| 98 | + av_frame_free(&ctx->last_avframe); | |
| 99 | + ctx->last_avframe = avframe; | |
| 100 | + | |
| 101 | + return 0; | |
| 102 | +} | |
| 103 | + | |
| 104 | +static int ndi_write_audio_packet(AVFormatContext *avctx, AVStream *st, AVPacket *pkt) | |
| 105 | +{ | |
| 106 | + struct NDIContext *ctx = avctx->priv_data; | |
| 107 | + | |
| 108 | + ctx->audio->p_data = (short *)pkt->data; | |
| 109 | + ctx->audio->timecode = av_rescale_q(pkt->pts, st->time_base, NDI_TIME_BASE_Q); | |
| 110 | + ctx->audio->no_samples = pkt->size / (ctx->audio->no_channels << 1); | |
| 111 | + | |
| 112 | + av_log(avctx, AV_LOG_DEBUG, "%s: pkt->pts=%"PRId64", timecode=%"PRId64", st->time_base=%d/%d\n", | |
| 113 | + __func__, pkt->pts, ctx->audio->timecode, st->time_base.num, st->time_base.den); | |
| 114 | + | |
| 115 | + NDIlib_util_send_send_audio_interleaved_16s(ctx->ndi_send, ctx->audio); | |
| 116 | + | |
| 117 | + return 0; | |
| 118 | +} | |
| 119 | + | |
| 120 | +static int ndi_write_packet(AVFormatContext *avctx, AVPacket *pkt) | |
| 121 | +{ | |
| 122 | + AVStream *st = avctx->streams[pkt->stream_index]; | |
| 123 | + | |
| 124 | + if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) | |
| 125 | + return ndi_write_video_packet(avctx, st, pkt); | |
| 126 | + else if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) | |
| 127 | + return ndi_write_audio_packet(avctx, st, pkt); | |
| 128 | + | |
| 129 | + return AVERROR_BUG; | |
| 130 | +} | |
| 131 | + | |
| 132 | +static int ndi_setup_audio(AVFormatContext *avctx, AVStream *st) | |
| 133 | +{ | |
| 134 | + struct NDIContext *ctx = avctx->priv_data; | |
| 135 | + AVCodecParameters *c = st->codecpar; | |
| 136 | + | |
| 137 | + if (ctx->audio) { | |
| 138 | + av_log(avctx, AV_LOG_ERROR, "Only one audio stream is supported!\n"); | |
| 139 | + return AVERROR(EINVAL); | |
| 140 | + } | |
| 141 | + | |
| 142 | + ctx->audio = av_mallocz(sizeof(NDIlib_audio_frame_interleaved_16s_t)); | |
| 143 | + if (!ctx->audio) | |
| 144 | + return AVERROR(ENOMEM); | |
| 145 | + | |
| 146 | + ctx->audio->sample_rate = c->sample_rate; | |
| 147 | + ctx->audio->no_channels = c->channels; | |
| 148 | + ctx->audio->reference_level = ctx->reference_level; | |
| 149 | + | |
| 150 | + avpriv_set_pts_info(st, 64, 1, NDI_TIME_BASE); | |
| 151 | + | |
| 152 | + return 0; | |
| 153 | +} | |
| 154 | + | |
| 155 | +static int ndi_setup_video(AVFormatContext *avctx, AVStream *st) | |
| 156 | +{ | |
| 157 | + struct NDIContext *ctx = avctx->priv_data; | |
| 158 | + AVCodecParameters *c = st->codecpar; | |
| 159 | + | |
| 160 | + if (ctx->video) { | |
| 161 | + av_log(avctx, AV_LOG_ERROR, "Only one video stream is supported!\n"); | |
| 162 | + return AVERROR(EINVAL); | |
| 163 | + } | |
| 164 | + | |
| 165 | + if (c->codec_id != AV_CODEC_ID_WRAPPED_AVFRAME) { | |
| 166 | + av_log(avctx, AV_LOG_ERROR, "Unsupported codec format!" | |
| 167 | + " Only AV_CODEC_ID_WRAPPED_AVFRAME is supported (-vcodec wrapped_avframe).\n"); | |
| 168 | + return AVERROR(EINVAL); | |
| 169 | + } | |
| 170 | + | |
| 171 | + if (c->format != AV_PIX_FMT_UYVY422 && c->format != AV_PIX_FMT_BGRA && | |
| 172 | + c->format != AV_PIX_FMT_BGR0 && c->format != AV_PIX_FMT_RGBA && | |
| 173 | + c->format != AV_PIX_FMT_RGB0) { | |
| 174 | + av_log(avctx, AV_LOG_ERROR, "Unsupported pixel format!" | |
| 175 | + " Only AV_PIX_FMT_UYVY422, AV_PIX_FMT_BGRA, AV_PIX_FMT_BGR0," | |
| 176 | + " AV_PIX_FMT_RGBA, AV_PIX_FMT_RGB0 is supported.\n"); | |
| 177 | + return AVERROR(EINVAL); | |
| 178 | + } | |
| 179 | + | |
| 180 | + if (c->field_order == AV_FIELD_BB || c->field_order == AV_FIELD_BT) { | |
| 181 | + av_log(avctx, AV_LOG_ERROR, "Lower field-first disallowed"); | |
| 182 | + return AVERROR(EINVAL); | |
| 183 | + } | |
| 184 | + | |
| 185 | + ctx->video = av_mallocz(sizeof(NDIlib_video_frame_t)); | |
| 186 | + if (!ctx->video) | |
| 187 | + return AVERROR(ENOMEM); | |
| 188 | + | |
| 189 | + switch(c->format) { | |
| 190 | + case AV_PIX_FMT_UYVY422: | |
| 191 | + ctx->video->FourCC = NDIlib_FourCC_type_UYVY; | |
| 192 | + break; | |
| 193 | + case AV_PIX_FMT_BGRA: | |
| 194 | + ctx->video->FourCC = NDIlib_FourCC_type_BGRA; | |
| 195 | + break; | |
| 196 | + case AV_PIX_FMT_BGR0: | |
| 197 | + ctx->video->FourCC = NDIlib_FourCC_type_BGRX; | |
| 198 | + break; | |
| 199 | + case AV_PIX_FMT_RGBA: | |
| 200 | + ctx->video->FourCC = NDIlib_FourCC_type_RGBA; | |
| 201 | + break; | |
| 202 | + case AV_PIX_FMT_RGB0: | |
| 203 | + ctx->video->FourCC = NDIlib_FourCC_type_RGBX; | |
| 204 | + break; | |
| 205 | + } | |
| 206 | + | |
| 207 | + ctx->video->xres = c->width; | |
| 208 | + ctx->video->yres = c->height; | |
| 209 | + ctx->video->frame_rate_N = st->avg_frame_rate.num; | |
| 210 | + ctx->video->frame_rate_D = st->avg_frame_rate.den; | |
| 211 | + ctx->video->frame_format_type = c->field_order == AV_FIELD_PROGRESSIVE | |
| 212 | + ? NDIlib_frame_format_type_progressive | |
| 213 | + : NDIlib_frame_format_type_interleaved; | |
| 214 | + | |
| 215 | + if (st->sample_aspect_ratio.num) { | |
| 216 | + AVRational display_aspect_ratio; | |
| 217 | + av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den, | |
| 218 | + st->codecpar->width * (int64_t)st->sample_aspect_ratio.num, | |
| 219 | + st->codecpar->height * (int64_t)st->sample_aspect_ratio.den, | |
| 220 | + 1024 * 1024); | |
| 221 | + ctx->video->picture_aspect_ratio = av_q2d(display_aspect_ratio); | |
| 222 | + } | |
| 223 | + else | |
| 224 | + ctx->video->picture_aspect_ratio = (double)st->codecpar->width/st->codecpar->height; | |
| 225 | + | |
| 226 | + avpriv_set_pts_info(st, 64, 1, NDI_TIME_BASE); | |
| 227 | + | |
| 228 | + return 0; | |
| 229 | +} | |
| 230 | + | |
| 231 | +static int ndi_write_header(AVFormatContext *avctx) | |
| 232 | +{ | |
| 233 | + int ret = 0; | |
| 234 | + unsigned int n; | |
| 235 | + struct NDIContext *ctx = avctx->priv_data; | |
| 236 | + const NDIlib_send_create_t ndi_send_desc = { .p_ndi_name = avctx->filename, | |
| 237 | + .p_groups = NULL, .clock_video = ctx->clock_video, .clock_audio = ctx->clock_audio }; | |
| 238 | + | |
| 239 | + if (!NDIlib_initialize()) { | |
| 240 | + av_log(avctx, AV_LOG_ERROR, "NDIlib_initialize failed.\n"); | |
| 241 | + return AVERROR_EXTERNAL; | |
| 242 | + } | |
| 243 | + | |
| 244 | + /* check if streams compatible */ | |
| 245 | + for (n = 0; n < avctx->nb_streams; n++) { | |
| 246 | + AVStream *st = avctx->streams[n]; | |
| 247 | + AVCodecParameters *c = st->codecpar; | |
| 248 | + if (c->codec_type == AVMEDIA_TYPE_AUDIO) { | |
| 249 | + if ((ret = ndi_setup_audio(avctx, st))) | |
| 250 | + goto error; | |
| 251 | + } else if (c->codec_type == AVMEDIA_TYPE_VIDEO) { | |
| 252 | + if ((ret = ndi_setup_video(avctx, st))) | |
| 253 | + goto error; | |
| 254 | + } else { | |
| 255 | + av_log(avctx, AV_LOG_ERROR, "Unsupported stream type.\n"); | |
| 256 | + ret = AVERROR(EINVAL); | |
| 257 | + goto error; | |
| 258 | + } | |
| 259 | + } | |
| 260 | + | |
| 261 | + ctx->ndi_send = NDIlib_send_create(&ndi_send_desc); | |
| 262 | + if (!ctx->ndi_send) { | |
| 263 | + av_log(avctx, AV_LOG_ERROR, "Failed to create NDI output %s\n", avctx->filename); | |
| 264 | + ret = AVERROR_EXTERNAL; | |
| 265 | + } | |
| 266 | + | |
| 267 | +error: | |
| 268 | + return ret; | |
| 269 | +} | |
| 270 | + | |
| 271 | +#define OFFSET(x) offsetof(struct NDIContext, x) | |
| 272 | +static const AVOption options[] = { | |
| 273 | + { "reference_level", "The audio reference level in dB" , OFFSET(reference_level), AV_OPT_TYPE_INT, { .i64 = 0 }, -20, 20, AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_AUDIO_PARAM}, | |
| 274 | + { "clock_video", "These specify whether video 'clock' themselves" , OFFSET(clock_video), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_VIDEO_PARAM }, | |
| 275 | + { "clock_audio", "These specify whether audio 'clock' themselves" , OFFSET(clock_audio), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_AUDIO_PARAM }, | |
| 276 | + { NULL }, | |
| 277 | +}; | |
| 278 | + | |
| 279 | +static const AVClass libndi_newtek_muxer_class = { | |
| 280 | + .class_name = "NDI muxer", | |
| 281 | + .item_name = av_default_item_name, | |
| 282 | + .option = options, | |
| 283 | + .version = LIBAVUTIL_VERSION_INT, | |
| 284 | + .category = AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT, | |
| 285 | +}; | |
| 286 | + | |
| 287 | +AVOutputFormat ff_libndi_newtek_muxer = { | |
| 288 | + .name = "libndi_newtek", | |
| 289 | + .long_name = NULL_IF_CONFIG_SMALL("Network Device Interface (NDI) output using NewTek library"), | |
| 290 | + .audio_codec = AV_CODEC_ID_PCM_S16LE, | |
| 291 | + .video_codec = AV_CODEC_ID_WRAPPED_AVFRAME, | |
| 292 | + .subtitle_codec = AV_CODEC_ID_NONE, | |
| 293 | + .flags = AVFMT_NOFILE, | |
| 294 | + .priv_class = &libndi_newtek_muxer_class, | |
| 295 | + .priv_data_size = sizeof(struct NDIContext), | |
| 296 | + .write_header = ndi_write_header, | |
| 297 | + .write_packet = ndi_write_packet, | |
| 298 | + .write_trailer = ndi_write_trailer, | |
| 299 | +}; | |
| @@ -28,8 +28,8 @@ · 27 unmodified lines | ||
| 28 | 28 | #include "libavutil/version.h" |
| 29 | 29 | |
| 30 | 30 | #define LIBAVDEVICE_VERSION_MAJOR 57 |
| 31 | −#define LIBAVDEVICE_VERSION_MINOR 7 | |
| 32 | −#define LIBAVDEVICE_VERSION_MICRO 101 | |
| 31 | +#define LIBAVDEVICE_VERSION_MINOR 8 | |
| 32 | +#define LIBAVDEVICE_VERSION_MICRO 100 | |
| 33 | 33 | |
| 34 | 34 | #define LIBAVDEVICE_VERSION_INT AV_VERSION_INT(LIBAVDEVICE_VERSION_MAJOR, \ |
| 35 | 35 | LIBAVDEVICE_VERSION_MINOR, \ |