Showing the changes to libavdevice (6 of 10 files). Show all
Unified · Split
| @@ -19,6 +19,8 @@ OBJS-$(CONFIG_BKTR_INDEV) += bktr.o · 18 unmodified lines | |||
| 19 | OBJS-$(CONFIG_CACA_OUTDEV) += caca.o | 19 | OBJS-$(CONFIG_CACA_OUTDEV) += caca.o |
| 20 | OBJS-$(CONFIG_DECKLINK_OUTDEV) += decklink_enc.o decklink_enc_c.o decklink_common.o | 20 | OBJS-$(CONFIG_DECKLINK_OUTDEV) += decklink_enc.o decklink_enc_c.o decklink_common.o |
| 21 | OBJS-$(CONFIG_DECKLINK_INDEV) += decklink_dec.o decklink_dec_c.o decklink_common.o | 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 | OBJS-$(CONFIG_DSHOW_INDEV) += dshow_crossbar.o dshow.o dshow_enummediatypes.o \ | 24 | OBJS-$(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.o | 26 | dshow_pin.o dshow_common.o |
| @@ -60,6 +62,8 @@ SLIBOBJS-$(HAVE_GNU_WINDRES) += avdeviceres.o · 35 unmodified lines | |||
| 60 | SKIPHEADERS += decklink_common.h | 62 | SKIPHEADERS += decklink_common.h |
| 61 | SKIPHEADERS-$(CONFIG_DECKLINK) += decklink_enc.h decklink_dec.h \ | 63 | SKIPHEADERS-$(CONFIG_DECKLINK) += decklink_enc.h decklink_dec.h \ |
| 62 | decklink_common_c.h | 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 | SKIPHEADERS-$(CONFIG_DSHOW_INDEV) += dshow_capture.h | 67 | SKIPHEADERS-$(CONFIG_DSHOW_INDEV) += dshow_capture.h |
| 64 | SKIPHEADERS-$(CONFIG_FBDEV_INDEV) += fbdev_common.h | 68 | SKIPHEADERS-$(CONFIG_FBDEV_INDEV) += fbdev_common.h |
| 65 | SKIPHEADERS-$(CONFIG_FBDEV_OUTDEV) += fbdev_common.h | 69 | SKIPHEADERS-$(CONFIG_FBDEV_OUTDEV) += fbdev_common.h |
| @@ -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); |
| @@ -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 | #include "libavutil/version.h" | 28 | #include "libavutil/version.h" |
| 29 | 29 | ||
| 30 | #define LIBAVDEVICE_VERSION_MAJOR 57 | 30 | #define LIBAVDEVICE_VERSION_MAJOR 57 |
| 31 | #define LIBAVDEVICE_VERSION_MINOR 7 | 31 | #define LIBAVDEVICE_VERSION_MINOR 8 |
| 32 | #define LIBAVDEVICE_VERSION_MICRO 101 | 32 | #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, \ |