[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