[FFmpeg-devel] Patch to add output libavdevice for Blackmagic DeckLink card
Nicolas George
nicolas.george at normalesup.org
Sat Feb 25 15:24:54 CET 2012
Le sextidi 6 ventôse, an CCXX, Deron a écrit :
> My first patch, so please be kind. This works well for me with
> everything I have thrown at it so far. It still has several items
> that are unfinished but I thought I would start getting some
> feedback. Areas I am unsure about are commented.
I do not know either c++ nor the card you are trying to support, but I may
be able to give some feedback on the parts I know.
Thanks for your work anyway.
> >From fa094a3a47c52d022be14aaf544ae9aa509cc42c Mon Sep 17 00:00:00 2001
> From: Deron Kazmaier <deron at pagestream.org>
> Date: Fri, 24 Feb 2012 10:37:23 -0700
> Subject: [PATCH] Add output libavdevice for Blackmagic DeckLink card.
>
> ---
> configure | 7 +
> libavdevice/Makefile | 1 +
> libavdevice/alldevices.c | 1 +
> libavdevice/decklink_enc.cpp | 720 ++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 729 insertions(+), 0 deletions(-)
> create mode 100644 libavdevice/decklink_enc.cpp
>
> diff --git a/configure b/configure
> index 7fdf0c0..4bcd07a 100755
> --- a/configure
> +++ b/configure
> @@ -167,6 +167,7 @@ Configuration options:
> External library support:
> --enable-avisynth enable reading of AVISynth script files [no]
> --enable-bzlib enable bzlib [autodetect]
> + --enable-decklink enable DeckLink output [no]
What happens if you also add input? Same option for both, and
--en/disable-device=decklinkdec to fine tune?
> --enable-frei0r enable frei0r video filtering
> --enable-gnutls enable gnutls [no]
> --enable-libaacplus enable AAC+ encoding via libaacplus [no]
> @@ -1012,6 +1013,7 @@ CONFIG_LIST="
> bzlib
> crystalhd
> dct
> + decklink
> doc
> dwt
> dxva2
> @@ -1609,6 +1611,8 @@ w64_demuxer_deps="wav_demuxer"
> alsa_indev_deps="alsa_asoundlib_h snd_pcm_htimestamp"
> alsa_outdev_deps="alsa_asoundlib_h"
> bktr_indev_deps_any="dev_bktr_ioctl_bt848_h machine_ioctl_bt848_h dev_video_bktr_ioctl_bt848_h dev_ic_bt8xx_h"
> +decklink_outdev_deps="decklink"
> +decklink_outdev_extralibs="-lstdc++"
As far as I could see, it also depends on pthreads.
> dshow_indev_deps="IBaseFilter"
> dshow_indev_extralibs="-lpsapi -lole32 -lstrmiids -luuid"
> dv1394_indev_deps="dv1394 dv_demuxer"
> @@ -3188,6 +3192,8 @@ enabled openssl && { check_lib openssl/ssl.h SSL_library_init -lssl -lcrypto
> check_lib openssl/ssl.h SSL_library_init -lssl32 -leay32 ||
> check_lib openssl/ssl.h SSL_library_init -lssl -lcrypto -lws2_32 -lgdi32 ||
> die "ERROR: openssl not found"; }
> +#enabled decklink && { check_header DeckLinkAPI.h && check_header DeckLinkAPIDispatch.cpp || die "ERROR: No version of DeckLinkAPI.h found."; }
> +
>
> # libdc1394 check
> if enabled libdc1394; then
> @@ -3508,6 +3514,7 @@ echo "new filter support ${avfilter-no}"
> echo "network support ${network-no}"
> echo "threading support ${thread_type-no}"
> echo "safe bitstream reader ${safe_bitstream_reader-no}"
> +echo "DeckLink support ${decklink-no}"
> echo "SDL support ${sdl-no}"
> echo "libdxva2 enabled ${dxva2-no}"
> echo "libva enabled ${vaapi-no}"
> diff --git a/libavdevice/Makefile b/libavdevice/Makefile
> index d7806ea..8131684 100644
> --- a/libavdevice/Makefile
> +++ b/libavdevice/Makefile
> @@ -14,6 +14,7 @@ OBJS-$(CONFIG_ALSA_INDEV) += alsa-audio-common.o \
> OBJS-$(CONFIG_ALSA_OUTDEV) += alsa-audio-common.o \
> alsa-audio-enc.o
> OBJS-$(CONFIG_BKTR_INDEV) += bktr.o
> +OBJS-$(CONFIG_DECKLINK_OUTDEV) += decklink_enc.o
> OBJS-$(CONFIG_DSHOW_INDEV) += dshow.o dshow_enummediatypes.o \
> dshow_enumpins.o dshow_filter.o \
> dshow_pin.o dshow_common.o
> diff --git a/libavdevice/alldevices.c b/libavdevice/alldevices.c
> index 86ebfee..a75d8e5 100644
> --- a/libavdevice/alldevices.c
> +++ b/libavdevice/alldevices.c
> @@ -41,6 +41,7 @@ void avdevice_register_all(void)
> REGISTER_INOUTDEV (ALSA, alsa);
> REGISTER_INDEV (BKTR, bktr);
> REGISTER_INDEV (DSHOW, dshow);
> + REGISTER_OUTDEV (DECKLINK, decklink);
> REGISTER_INDEV (DV1394, dv1394);
> REGISTER_INDEV (FBDEV, fbdev);
> REGISTER_INDEV (JACK, jack);
> diff --git a/libavdevice/decklink_enc.cpp b/libavdevice/decklink_enc.cpp
> new file mode 100644
> index 0000000..034d3cf
> --- /dev/null
> +++ b/libavdevice/decklink_enc.cpp
> @@ -0,0 +1,720 @@
> +/*
> + * Blackmagic DeckLink output device
> + * Copyright (c) 2012 Deron Kazmaier
> + * This code was created with help from the sdl and alsa devices,
> + * DeckLink examples, and a generic player written by Georg Lippitsch.
> + * Credit where credit due.
Indentation is strange.
> + *
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +
> +/**
> + * @file
> + * Blackmagic DeckLink output device
> + */
> +
> +/*
> + * example:
> + * ffmpeg -i INPUT -f decklink -
> + * ffmpeg -i INPUT -vcodec rawvideo -pix_fmt uyvy422 -f decklink -device 0 -
> + * ffmpeg -i "BLUE LAGOON PROMO ProRes Lite 71 Mbps.mov" -f lavfi
> + -i "amovie=BLUE LAGOON PROMO ProRes Lite 71 Mbps.mov, volume=-12dB"
> + -vcodec rawvideo -pix_fmt uyvy422 -ac 2 -ar 48000 -f decklink -device 0 -
> + * Ugg. What needs to be done to have audio filters without this mess!
> + *
> + */
> +
> +#include "DeckLinkAPI.h"
> +#include "DeckLinkAPIDispatch.cpp"
> +#include "pthread.h"
> +
> +extern "C" {
> +#include "libavutil/avstring.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/parseutils.h"
> +#include "libavutil/pixdesc.h"
> +#include "libswscale/swscale.h"
> +#include "avdevice.h"
> +}
> +
> +/*
> + * This needs to be the exact string that the device names the
> + * display mode. Better would to select based on w/h/fps ??
> + */
> +#define DISPLAY_MODE "HD 720p 59.94" //"HD 1080i 59.94"
Unless someone who knows the device gives good advice, maybe you should
explain what it does.
> +#define NUM_PREROLL 20
What does that means?
> +
> +typedef struct {
> + AVClass *avclass;
> + AVFormatContext *s;
> + SwsContext* sws;
Is it really necessary to scale internally? It could lead to several
successive scalings, which is never a good thing.
> + int video_stream_index;
> + int audio_stream_index;
> + int audio_channels;
> +
> + IDeckLinkOutput *out;
> + class DeckLinkVideoOutputCallback *outputcallback;
> + int device;
> + BMDDisplayMode display_mode;
> + enum PixelFormat frame_pixfmt;
> + BMDPixelFormat frame_bmdpixfmt;
> + int frame_width;
> + int frame_height;
> + BMDTimeValue frame_duration;
> + BMDTimeScale frame_timescale;
> + bool playing_started;
> +
> + pthread_mutex_t mutex;
> + pthread_cond_t cond;
> + IDeckLinkMutableVideoFrame *frame[NUM_PREROLL];
> + int frame_number;
> + int frames_buffered;
> +} DeckLinkContext;
> +
> +/*
> + * this is not currently used. Right now, all output is made in
> + * bmdFormat8BitYUV. It would be nice to either auto select the closest format,
> + * or allow the user to select the intended format. Something like -pix_fmt auto
> + * or -pix_fmt uyvy422 ??
> + */
> +static const struct decklink_bmd_pix_fmt_entry {
> + enum PixelFormat pix_fmt; BMDPixelFormat bmd_fmt;
> +} decklink_bmd_pix_fmt_map[] = {
> + { PIX_FMT_UYVY422, bmdFormat8BitYUV }, /* ???UYVY??? 4:2:2 Representation */
> + { PIX_FMT_ARGB, bmdFormat8BitARGB }, /* ARGB(orRGB32)4:4:4:x raw */
> + { PIX_FMT_BGRA, bmdFormat8BitBGRA }, /* BGRA 4:4:4:x raw */
> + { PIX_FMT_NONE, bmdFormat10BitYUV }, /* ???v210???4:2:2 Representation */
PIX_FMT_YUV420P10LE or PIX_FMT_YUV444P10LE?
> + { PIX_FMT_NONE, bmdFormat10BitRGB }, /* ???r210???4:4:4raw */
> + { PIX_FMT_NONE, 0 },
> +};
Since you could build a map, using the pixel format specified in the codec
structure should work. In the command line tool, the -pix_fmt would do the
work, as you suspect.
> +
> +
> +class DeckLinkVideoOutputCallback : public IDeckLinkVideoOutputCallback
> +{
> +private:
> + DeckLinkContext *m_decklink;
> +
> +public:
> + DeckLinkVideoOutputCallback(DeckLinkContext *decklink)
> + {
> + m_decklink = decklink;
> + }
> +
> + HRESULT ScheduledFrameCompleted(IDeckLinkVideoFrame *frame,
> + BMDOutputFrameCompletionResult result)
> + {
> + //av_log(m_decklink->s, AV_LOG_DEBUG, "Frame completed.\n");
> +
> + switch (result)
> + {
The recommended style is to have the opening brace on the same line (except
for functions body). You used it yourself in various parts of the code:
consistency is best.
> + case bmdOutputFrameCompleted:
> + case bmdOutputFrameFlushed:
> + break;
> + case bmdOutputFrameDropped:
> + av_log(m_decklink->s, AV_LOG_ERROR, "Frame dropped.\n");
> + break;
> + case bmdOutputFrameDisplayedLate:
> + av_log(m_decklink->s, AV_LOG_ERROR, "Frame late.\n");
> + break;
> + }
> +
> + pthread_mutex_lock(&m_decklink->mutex);
> + (m_decklink->frames_buffered)--;
> + pthread_cond_signal(&m_decklink->cond);
> + pthread_mutex_unlock(&m_decklink->mutex);
> +
> + return S_OK;
> + }
> +
> + HRESULT ScheduledPlaybackHasStopped()
> + {
> + return S_OK;
> + }
> +
> + HRESULT QueryInterface(REFIID iid, LPVOID *ppv)
> + {
> + return E_NOINTERFACE;
> + }
> +
> + // AddRef,Release not needed
> + ULONG AddRef()
> + {
> + return 1;
> + }
> +
> + ULONG Release()
> + {
> + return 0;
> + }
> +};
> +
> +
> +static int decklink_write_trailer(AVFormatContext *s)
av_cold missing.
> +{
> + DeckLinkContext *decklink = (DeckLinkContext *)s->priv_data;
> + int i;
> +
> + if (decklink->sws) sws_freeContext(decklink->sws);
> +
> + // Stop playing, wait for scheduled frames
> + if (decklink->playing_started)
> + decklink->out->StopScheduledPlayback(
> + decklink->frame_number * decklink->frame_duration,
> + NULL,
> + decklink->frame_timescale);
> +
> + if (decklink->frames_buffered)
> + {
> + pthread_mutex_lock(&decklink->mutex);
> + while (decklink->frames_buffered) {
> + pthread_cond_wait(&decklink->cond, &decklink->mutex);
> + }
> + pthread_mutex_unlock(&decklink->mutex);
> + }
> +
> + // Release the cache frames.
> + for (i = 0; i < NUM_PREROLL; i++) {
> + if (decklink->frame[i]) decklink->frame[i]->Release();
> + decklink->frame[i] = NULL;
> + }
> +
> + if (decklink->out) {
> + decklink->out->DisableAudioOutput();
> + decklink->out->DisableVideoOutput();
> + decklink->out->Release();
> + }
> +
> + /*
> + * Does decklink->outputcallback need to be released?
> + * Not a big c++ fan...
> + */
> +
> + return 0;
> +}
> +
> +
> +static int decklink_write_header(AVFormatContext *s)
av_cold.
> +{
> + IDeckLinkDisplayModeIterator *dldmi;
> + IDeckLinkDisplayMode *dldm;
> + IDeckLinkIterator *dli;
> + IDeckLink *dl;
> + DeckLinkContext *decklink = (DeckLinkContext *)s->priv_data;
> + AVStream *vst;
> + AVCodecContext *vencctx, *aencctx;
> + const char *mode_name;
> + float sar, dar;
> + int i, ret;
> +
> + decklink->s = s;
> + pthread_mutex_init(&decklink->mutex, NULL);
> + pthread_cond_init(&decklink->cond, NULL);
> + decklink->outputcallback = new DeckLinkVideoOutputCallback(decklink);
> +
> + /*
> + * Find the best video and audio stream.
> + * It is possible to output just video, so maybe
> + * future revision will allow that if anyone
> + * can see a reason why that should be allowed...
> + */
> + ret = av_find_best_stream(s, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
> + if (ret < 0) {
> + av_log(NULL, AV_LOG_ERROR, "Cannot find a video stream in the input.\n");
> + goto fail;
> + }
> + decklink->video_stream_index = ret;
> + vst = s->streams[decklink->video_stream_index];
> + vencctx = vst->codec;
> +
> + ret = av_find_best_stream(s, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
> + if (ret < 0) {
> + av_log(NULL, AV_LOG_ERROR, "Cannot find an audio stream in the input.\n");
> + goto fail;
> + }
> + decklink->audio_stream_index = ret;
av_find_best_stream was not designed for the formats themselves, although
you managed to make use of it.
Unless I am mistaken, if your device is set up with several audio or video
streams, or with a subtitles stream, the extra streams will be silently
ignored. This is, IMHO, not a good thing.
I believe you should rather simply fail unless there is exactly 2 channels
and #0 is video and #1 is audio.
> +
> +
> +
> +
> + /*
> + * Is it even possible to get something besides RAWVIDEO since that
> + * is what is requested in the AVOutputFormat structure.
> + */
Yes, it is possible: this field is advisory. For example, the ALSA outdev
advises for S16_NE, but "-acodec pcm_s32le -f alsa default" works.
> + if (vencctx->codec_id != CODEC_ID_RAWVIDEO) {
> + av_log(s, AV_LOG_ERROR, "Only supports rawvideo stream\n");
> + ret = AVERROR(EINVAL);
> + goto fail;
> + }
> +
> + aencctx = s->streams[decklink->audio_stream_index]->codec;;
> +
> + /*
> + * this part should be replaced with code to force audio stream
> + * into 48kHz, 2/8/16 channel output. Seems such a pain to
> + * have to always be forcing streams to match the correct
> + * output.
> + */
> +
> + /*
> + * Is it even possible to get something besides PCM_S16LE since that
> + * is what is requested in the AVOutputFormat structure.
> + */
> + if (aencctx->codec_id != CODEC_ID_PCM_S16LE) {
> + av_log(s, AV_LOG_ERROR, "Only supports s16le audio stream\n");
> + ret = AVERROR(EINVAL);
> + goto fail;
> + }
> +
> + if (aencctx->sample_rate != 48000) {
> + av_log(s, AV_LOG_ERROR, "Only supports 48kHz audio stream\n");
> + ret = AVERROR(EINVAL);
> + goto fail;
> + }
> +
> + if ((aencctx->channels != 2) &&
> + (aencctx->channels != 8) &&
> + (aencctx->channels != 16)) {
> + av_log(s, AV_LOG_ERROR,
> + "Only supports 2, 8, or 16 channel audio stream\n");
> + ret = AVERROR(EINVAL);
> + goto fail;
> + }
> + decklink->audio_channels = aencctx->channels;
I believe it is fine like that: again, you do not want the application to
stack several resampling/rematrixing steps when one would suffice.
> +
> +/*
> + This code should either allow the user to select a specific bmdFormat or
> + auto, where the best/closest output bmdFormat is picked based on
> + vencctx->pix_fmt.
> +
> + for (i = 0; decklink_bmd_pix_fmt_map[i].pix_fmt != PIX_FMT_NONE; i++) {
> + if (decklink_bmd_pix_fmt_map[i].pix_fmt == encctx->pix_fmt) {
> + decklink->frame_bmd_fmt = decklink_bmd_pix_fmt_map[i].bmd_fmt;
> + break;
> + }
> + }
> +
> + if (!decklink->frame_pixfmt) {
> + av_log(s, AV_LOG_ERROR,
> + "Unsupported pixel format '%s', choose one of [enum list?].\n",
> + av_get_pix_fmt_name(encctx->pix_fmt));
> + ret = AVERROR(EINVAL);
> + goto fail;
> + }
> +*/
> +
> + decklink->frame_bmdpixfmt = bmdFormat8BitYUV;
> + decklink->frame_pixfmt = PIX_FMT_UYVY422;
> +
> + /*
> + * Inits the decklink card, loads the library and creates the iterator
> + * to walk through the list of attached DeckLink "devices".
> + * I think port might be a better word, but devices is what is used in
> + * many DeckLink examples.
> + */
> + dli = CreateDeckLinkIteratorInstance();
> + if (!dli)
> + {
> + av_log(s, AV_LOG_ERROR,
> + "Error opening Decklink driver.\n");
> + ret = AVERROR(EINVAL);
> + goto fail;
> + }
Does that library provide some insight about what went wrong somewhere? The
same goes at several places below.
> +
> + /*
> + * Select the output device based on command line option
> + * -device (0...n).
> + * Some DeckLink cards will have as many as 4 outputs
> + * and outputs are numbered starting at lowest PCIe
> + * slot.
> + */
> + dl = NULL;
> + i = 0;
> + while (dli->Next(&dl) == S_OK)
> + {
> + if (i == decklink->device)
> + {
> + if (dl->QueryInterface(
> + IID_IDeckLinkOutput,
> + (void**)&decklink->out)
> + != S_OK)
> + {
> + dl->Release();
> + dli->Release();
> + goto fail;
> + }
> + }
> +
> + dl->Release();
> + i++;
> + }
> + dli->Release();
> +
> + if (!decklink->out)
> + {
> + av_log(s, AV_LOG_ERROR,
> + "Unable to initialize Decklink device %d\n", decklink->device);
> + ret = AVERROR(EINVAL);
> + goto fail;
> + }
> +
> + /*
> + * Get the display mode iterator to find the list of modes this device
> + * supports. Not every device will support all display modes.
> + */
> +
> + if (decklink->out->GetDisplayModeIterator(&dldmi) != S_OK)
> + {
> + av_log(s, AV_LOG_ERROR, "Error retrieving display mode iterator\n");
> + ret = AVERROR(EINVAL);
> + goto fail;
> + }
> + dldm = NULL;
> + while (dldmi->Next(&dldm) == S_OK)
> + {
> + dldm->GetName(&mode_name);
> + /* DISPLAY_MODE needs to be configurable */
> + if (strcmp(mode_name, DISPLAY_MODE) == 0)
> + {
> + decklink->display_mode = dldm->GetDisplayMode();
> + dldm->GetFrameRate(
> + &decklink->frame_duration,
> + &decklink->frame_timescale);
> + decklink->frame_width = dldm->GetWidth();
> + decklink->frame_height = dldm->GetHeight();
> + }
> + av_free((void *)mode_name); /* this was free, but free is !defined! */
> + dldm->Release();
> + }
> + dldmi->Release();
> + if (!decklink->display_mode)
> + {
> + av_log(s, AV_LOG_ERROR, "Error retrieving display mode\n");
> + ret = AVERROR(EINVAL);
> + goto fail;
> + }
> +
> + /*
> + * Create an array of frames. For now, using the standard decklink frame.
> + * It may be necessary to create a buffer of our own for unscheduled frames
> + * depending on potential timing problems.
> + */
> + for (i = 0; i < NUM_PREROLL; i++)
> + {
> + if ((ret = decklink->out->CreateVideoFrame(
> + decklink->frame_width,
> + decklink->frame_height,
> + decklink->frame_width * 2,
> + decklink->frame_bmdpixfmt,
> + bmdFrameFlagDefault,
> + &decklink->frame[i])) != S_OK)
> + {
> + av_log(s, AV_LOG_ERROR, "Error %X creating video frame %d\n", ret, i);
> + ret = AVERROR(EINVAL);
> + goto fail;
> + }
> + }
> +
> + /*
> + * Compute overlay width and height from the codec context information.
> + * This is unused at this point. The code should cause the frames to be
> + * scaled up or down as necessary so that it fits with no loss and aspect
> + * ratio maintained. How is this best done?
> + *
> + */
> + sar = vst->sample_aspect_ratio.num ? av_q2d(vst->sample_aspect_ratio) : 1;
> + dar = sar * (float)vencctx->width / (float)vencctx->height;
> +
> +/*
> + decklink->overlay_height = encctx->height;
> + decklink->overlay_width = ((int)rint(decklink->overlay_height * dar));
> + if (decklink->overlay_width > encctx->width) {
> + decklink->overlay_width = encctx->width;
> + decklink->overlay_height = ((int)rint(sdl->overlay_width / dar));
> + }
> +*/
> +
> + /*
> + * It seems that this private data is zeroed on creation. If
> + * that is true, then these are obviously not necessary.
> + */
> + decklink->sws = NULL;
> + decklink->frame_number = 0;
> + decklink->frames_buffered = 0;
> + decklink->playing_started = false;
> +
> + if (decklink->out->SetScheduledFrameCompletionCallback(
> + decklink->outputcallback) != S_OK)
> + {
> + av_log(s, AV_LOG_ERROR, "Error failed to set frame completion callback\n");
> + ret = AVERROR(EINVAL);
> + goto fail;
> + }
> +
> + if ((ret = decklink->out->EnableVideoOutput(decklink->display_mode,
> + bmdVideoOutputFlagDefault)) != S_OK)
> + {
> + av_log(s, AV_LOG_ERROR,
> + "Error (%X) could not enable video output display mode:0x%X\n",
> + ret, decklink->display_mode);
> + ret = AVERROR(EINVAL);
> + goto fail;
> + }
> +
> + if (decklink->out->EnableAudioOutput(
> + bmdAudioSampleRate48kHz,
> + bmdAudioSampleType16bitInteger,
> + aencctx->channels,
> + bmdAudioOutputStreamContinuous) != S_OK) {
> + av_log(s, AV_LOG_ERROR, "Error could not enable audio output\n");
> + ret = AVERROR(EINVAL);
> + goto fail;
> + }
> +
> + decklink->out->BeginAudioPreroll();
> +
> + /*
> + * At this point, any audio or video submitted is held for until playback
> + * is started. This will happen once enough video frames are submitted.
> + */
> +
> + av_log(s, AV_LOG_INFO,
> + "w:%d h:%d fmt:%s sar:%f -> device:%d w:%d h:%d fmt:%s dar:%f\n",
> + vencctx->width, vencctx->height,
> + av_get_pix_fmt_name(vencctx->pix_fmt), sar,
> + decklink->device, decklink->frame_width, decklink->frame_height,
> + av_get_pix_fmt_name(decklink->frame_pixfmt), dar);
> + return 0;
> +
> +
> +fail:
> + decklink_write_trailer(s);
> + return ret;
> +}
> +
> +static int decklink_write_packet(AVFormatContext *s, AVPacket *pkt)
> +{
> + DeckLinkContext *decklink = (DeckLinkContext *)s->priv_data;
> + AVCodecContext *encctx = s->streams[0]->codec;
> + AVPicture pic;
> + uint32_t nb_samples, samplesWritten;
Urgh the camelCase.
> + uint8_t *buf;
> + int i;
> +
> + if (pkt->stream_index == decklink->video_stream_index)
> + {
> + pthread_mutex_lock(&decklink->mutex);
> +
> + while (decklink->frames_buffered >= NUM_PREROLL)
> + {
> + if (!decklink->playing_started)
> + {
> + av_log(s, AV_LOG_INFO,
> + "starting play. stream timescale %d/%d or %d/%d\n",
> + encctx->time_base.num,
> + encctx->time_base.den,
> + s->streams[0]->time_base.num,
> + s->streams[0]->time_base.den);
> +
> + decklink->out->EndAudioPreroll();
> + if (pkt->pts == (int64_t)AV_NOPTS_VALUE)
> + {
> + decklink->out->StartScheduledPlayback(
> + 0,
> + decklink->frame_timescale,
> + 1.0);
> + }
> + else
> + {
> + decklink->out->StartScheduledPlayback(
> + 0,
> + (BMDTimeScale)s->streams[0]->time_base.den,
> + 1.0);
> + }
> + decklink->playing_started = true;
> + }
> +
> + pthread_cond_wait(&decklink->cond, &decklink->mutex);
> + }
> +
> + decklink->frame[decklink->frame_number % NUM_PREROLL]->GetBytes((void**)&buf);
Although it only starts to be a risk after 400 days, could you make
frame_number unsigned to avoid the braindeadness of the % operator with
regard to negative input? And possibly make NUM_PREROLL a power of 2. Or
keep a separate counter for the index in the cyclic buffer.
> +
> + avpicture_fill(
> + &pic, pkt->data, encctx->pix_fmt, encctx->width, encctx->height);
> +
> +/*
> + const AVPixFmtDescriptor *desc;
> + desc = &av_pix_fmt_descriptors[encctx->pix_fmt];
> +*/
> +
> +/*
> + * this is modeled after code by Georg Lippitsch. I can see where planes are
> + * used etc., but is unclear to me what needs to be done here exactly. Tests
> + * so far only require the second half to be used. Perhaps because of the
> + * command line use of -pix_fmt uyvy422?
> + */
> + if (false) //pic.interlaced_frame
> + {
> + decklink->sws = sws_getCachedContext(decklink->sws, encctx->width,
> + encctx->height / 2, encctx->pix_fmt, decklink->frame_width,
> + decklink->frame_height / 2, decklink->frame_pixfmt,
> + SWS_BILINEAR, NULL, NULL, NULL);
> +
> + uint8_t *src[] = {pic.data[0], pic.data[1], pic.data[2]};
> + int srcStride[] = {pic.linesize[0] * 2,
> + pic.linesize[1] * 2,
> + pic.linesize[2] * 2};
> + int dstStride[] = {decklink->frame_width * 4};
> +
> + sws_scale(decklink->sws, src, srcStride, 0, encctx->height / 2,
> + &buf, dstStride);
> + for (i = 0; i < 3; i++)
> + {
> + src[i] += pic.linesize[i];
> + }
> + buf += decklink->frame_width * 2;
> + sws_scale(decklink->sws, src, srcStride, 0, encctx->height / 2,
> + &buf, dstStride);
> + }
> + else
> + {
> + decklink->sws = sws_getCachedContext(decklink->sws, encctx->width,
> + encctx->height, encctx->pix_fmt, decklink->frame_width,
> + decklink->frame_height, decklink->frame_pixfmt,
> + SWS_BILINEAR, NULL, NULL, NULL);
> +
> + int dstStride[] = {decklink->frame_width * 2};
> +
> + sws_scale(decklink->sws, pic.data, pic.linesize, 0, encctx->height,
> + &buf, dstStride);
> + }
Unless I am mistaken, this part of the code is there to stop mid-stream
resolution changes, isn't it?
> +
> +
> + av_log(s, AV_LOG_DEBUG,
> + "schedule video frame #%d pts:%ld duration:%d (%ld %ld %ld)\n",
> + decklink->frame_number, pkt->pts, pkt->duration,
> + (BMDTimeValue)pkt->pts * (BMDTimeScale)s->streams[0]->time_base.num,
> + (BMDTimeValue)pkt->duration * (BMDTimeScale)encctx->time_base.num,
> + (BMDTimeScale)s->streams[0]->time_base.num * (BMDTimeScale)s->streams[0]->time_base.den);
> +
> + if (pkt->pts == (int64_t)AV_NOPTS_VALUE)
> + {
> + decklink->out->ScheduleVideoFrame(
> + decklink->frame[decklink->frame_number % NUM_PREROLL],
> + decklink->frame_number * decklink->frame_duration,
> + decklink->frame_duration, decklink->frame_timescale);
> + }
> + else
> + {
> + decklink->out->ScheduleVideoFrame(
> + decklink->frame[decklink->frame_number % NUM_PREROLL],
> + (BMDTimeValue)decklink->frame_number * (BMDTimeScale)pkt->duration,
> + (BMDTimeValue)pkt->duration,
> + (BMDTimeScale)s->streams[0]->time_base.den);
> +
> + /*
> + * This would be the correct code to use, as far as I understand,
> + * but pkt->pts + pkt+duration can be greater than nextpkt->pts and
> + * this makes DeckLink drop frames and otherwise perform erraticly.
> + * Probably the best solution/hack is to keep the expected pts and
> + * if over by 1 then adjust pts/duration for this frame by 1.
> +
> + decklink->out->ScheduleVideoFrame(
> + decklink->frame[decklink->frame_number % NUM_PREROLL],
> + (BMDTimeValue)pkt->pts * (BMDTimeScale)s->streams[0]->time_base.num,
> + (BMDTimeValue)pkt->duration * (BMDTimeScale)encctx->time_base.num,
> + (BMDTimeScale)s->streams[0]->time_base.num * (BMDTimeScale)s->streams[0]->time_base.den);
> +
> + */
> + }
> +
> + decklink->frames_buffered++;
> + decklink->frame_number++;
> +
> + pthread_mutex_unlock(&decklink->mutex);
> + }
> + else if (pkt->stream_index == decklink->audio_stream_index)
> + {
> + pthread_mutex_lock(&decklink->mutex);
> +
> + nb_samples = pkt->size / (decklink->audio_channels * 2);
> + samplesWritten = 0;
> +
> + av_log(s, AV_LOG_DEBUG,
> + "schedule audio data nb_samples:%d channels:%d pts:%ld duration:%d\n",
> + nb_samples, decklink->audio_channels, pkt->pts, pkt->duration);
> +
> + decklink->out->ScheduleAudioSamples(pkt->data, nb_samples, 0,
> + bmdAudioSampleRate48kHz,
> + &samplesWritten);
> +
> + /*
> + * This may also require that audio be buffered. Simple testing of
> + * this code has not shown buffering to be necessary but previous
> + * work has required this.
> + */
> + if (samplesWritten != nb_samples)
> + {
> + av_log(s, AV_LOG_ERROR,
> + "Audio samples not all written! (%d of %d accepted)\n",
> + samplesWritten, nb_samples);
> + }
> +
> + pthread_mutex_unlock(&decklink->mutex);
> + }
> +
> + return 0;
> +}
> +
> +#define OFFSET(x) offsetof(DeckLinkContext,x)
> +
> +static const AVOption options[] = {
> + { "device", "Decklink device", OFFSET(device), AV_OPT_TYPE_INT, { 0 }, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM },
> + { NULL },
> +};
> +
> +static const AVClass decklink_class = {
> + /* .class_name = */ "decklink output device",
> + /* .item_name = */ av_default_item_name,
> + /* .option = */ options,
> + /* .version = */ LIBAVUTIL_VERSION_INT,
> + /* log_level_offset_offset = */ 0,
> + /* parent_log_context_offset = */ 0,
> + /* child_next = */ NULL,
> + /* child_class_next = */ NULL
> +};
> +
> +AVOutputFormat ff_decklink_muxer = {
> + /* .name = */ "decklink",
> + /* .long_name = */ NULL_IF_CONFIG_SMALL("Blackmagic DeckLink output"),
> + /* .mime_type = */ NULL,
> + /* .extensions = */ NULL,
> + /* .audio_codec = */ CODEC_ID_PCM_S16LE,
> + /* .video_codec = */ CODEC_ID_RAWVIDEO,
> + /* .subtitle_codec = */ CODEC_ID_NONE,
> + /* .flags = */ AVFMT_NOFILE,
> + /* .codec_tag = */ NULL,
> + /* .priv_class = */ &decklink_class,
> + /* .next = */ NULL,
> + /* .priv_data_size = */ sizeof(DeckLinkContext),
> + /* .write_header = */ decklink_write_header,
> + /* .write_packet = */ decklink_write_packet,
> + /* .write_trailer = */ decklink_write_trailer,
> + /* .interleave_packet = */ NULL,
> + /* .query_codec = */ NULL,
> + /* .get_output_timestamp = */ NULL
> +};
> +
Hope this helps.
Regards,
--
Nicolas George
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 198 bytes
Desc: Digital signature
URL: <http://ffmpeg.org/pipermail/ffmpeg-devel/attachments/20120225/08282c52/attachment.asc>
More information about the ffmpeg-devel
mailing list