History

lavd: implement NewTek NDI input/output device support

Maksym Veremeyenko committed 9 years ago

Commit 26349271 parent 84ee65110 files · +779 −2Browse files

Showing the changes to libavdevice (6 of 10 files). Show all

Unified · Split

6 files
  1. libavdevice/Makefile +4 −0
  2. libavdevice/alldevices.c +1 −0
  3. libavdevice/libndi_newtek_common.h +30 −0
  4. libavdevice/libndi_newtek_dec.c +340 −0
  5. libavdevice/libndi_newtek_enc.c +299 −0
  6. libavdevice/version.h +2 −2
libavdevice/Makefile+4−0
@@ -19,6 +19,8 @@ OBJS-$(CONFIG_BKTR_INDEV) += bktr.o · 18 unmodified lines
19OBJS-$(CONFIG_CACA_OUTDEV) += caca.o19OBJS-$(CONFIG_CACA_OUTDEV) += caca.o
20OBJS-$(CONFIG_DECKLINK_OUTDEV) += decklink_enc.o decklink_enc_c.o decklink_common.o20OBJS-$(CONFIG_DECKLINK_OUTDEV) += decklink_enc.o decklink_enc_c.o decklink_common.o
21OBJS-$(CONFIG_DECKLINK_INDEV) += decklink_dec.o decklink_dec_c.o decklink_common.o21OBJS-$(CONFIG_DECKLINK_INDEV) += decklink_dec.o decklink_dec_c.o decklink_common.o
22OBJS-$(CONFIG_LIBNDI_NEWTEK_OUTDEV) += libndi_newtek_enc.o
23OBJS-$(CONFIG_LIBNDI_NEWTEK_INDEV) += libndi_newtek_dec.o
22OBJS-$(CONFIG_DSHOW_INDEV) += dshow_crossbar.o dshow.o dshow_enummediatypes.o \24OBJS-$(CONFIG_DSHOW_INDEV) += dshow_crossbar.o dshow.o dshow_enummediatypes.o \
23 dshow_enumpins.o dshow_filter.o \25 dshow_enumpins.o dshow_filter.o \
24 dshow_pin.o dshow_common.o26 dshow_pin.o dshow_common.o
@@ -60,6 +62,8 @@ SLIBOBJS-$(HAVE_GNU_WINDRES) += avdeviceres.o · 35 unmodified lines
60SKIPHEADERS += decklink_common.h62SKIPHEADERS += decklink_common.h
61SKIPHEADERS-$(CONFIG_DECKLINK) += decklink_enc.h decklink_dec.h \63SKIPHEADERS-$(CONFIG_DECKLINK) += decklink_enc.h decklink_dec.h \
62 decklink_common_c.h64 decklink_common_c.h
65SKIPHEADERS-$(CONFIG_LIBNDI_NEWTEK_INDEV) += libndi_newtek_common.h
66SKIPHEADERS-$(CONFIG_LIBNDI_NEWTEK_OUTDEV) += libndi_newtek_common.h
63SKIPHEADERS-$(CONFIG_DSHOW_INDEV) += dshow_capture.h67SKIPHEADERS-$(CONFIG_DSHOW_INDEV) += dshow_capture.h
64SKIPHEADERS-$(CONFIG_FBDEV_INDEV) += fbdev_common.h68SKIPHEADERS-$(CONFIG_FBDEV_INDEV) += fbdev_common.h
65SKIPHEADERS-$(CONFIG_FBDEV_OUTDEV) += fbdev_common.h69SKIPHEADERS-$(CONFIG_FBDEV_OUTDEV) += fbdev_common.h
libavdevice/alldevices.c+1−0
@@ -46,6 +46,7 @@ static void register_all(void) · 45 unmodified lines
46 REGISTER_INDEV (BKTR, bktr);46 REGISTER_INDEV (BKTR, bktr);
47 REGISTER_OUTDEV (CACA, caca);47 REGISTER_OUTDEV (CACA, caca);
48 REGISTER_INOUTDEV(DECKLINK, decklink);48 REGISTER_INOUTDEV(DECKLINK, decklink);
49 REGISTER_INOUTDEV(LIBNDI_NEWTEK, libndi_newtek);
49 REGISTER_INDEV (DSHOW, dshow);50 REGISTER_INDEV (DSHOW, dshow);
50 REGISTER_INDEV (DV1394, dv1394);51 REGISTER_INDEV (DV1394, dv1394);
51 REGISTER_INOUTDEV(FBDEV, fbdev);52 REGISTER_INOUTDEV(FBDEV, fbdev);
libavdevice/libndi_newtek_common.h+30−0
@@ -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
libavdevice/libndi_newtek_dec.c+340−0
@@ -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
29struct 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
45static 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
68static 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
95static 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
139static 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
179static 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
235static 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
258static 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
300static 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
316static 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
323static 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
331AVInputFormat 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};
libavdevice/libndi_newtek_enc.c+299−0
@@ -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
29struct 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
42static 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
57static 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
104static 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
120static 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
132static 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
155static 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
231static 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
267error:
268 return ret;
269}
270
271#define OFFSET(x) offsetof(struct NDIContext, x)
272static 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
279static 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
287AVOutputFormat 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};
libavdevice/version.h+2−2
@@ -28,8 +28,8 @@ · 27 unmodified lines
28#include "libavutil/version.h"28#include "libavutil/version.h"
29 29
30#define LIBAVDEVICE_VERSION_MAJOR 5730#define LIBAVDEVICE_VERSION_MAJOR 57
31#define LIBAVDEVICE_VERSION_MINOR 731#define LIBAVDEVICE_VERSION_MINOR 8
32#define LIBAVDEVICE_VERSION_MICRO 10132#define LIBAVDEVICE_VERSION_MICRO 100
33 33
34#define LIBAVDEVICE_VERSION_INT AV_VERSION_INT(LIBAVDEVICE_VERSION_MAJOR, \34#define LIBAVDEVICE_VERSION_INT AV_VERSION_INT(LIBAVDEVICE_VERSION_MAJOR, \
35 LIBAVDEVICE_VERSION_MINOR, \35 LIBAVDEVICE_VERSION_MINOR, \

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t