LoginSignup
2
0

More than 3 years have passed since last update.

D言語で動画再生

Posted at

はじめに

D言語アドベントカレンダー2020 12日目の記事(今回)です。
5日目の記事(前回)の続編的な内容です。

前回の記事では、音楽再生を実装しました。
今回の記事では、続けて、動画再生を実装します。
開発環境の構築情報は前回と同じため、今回は省略しています。興味がある方は、前回の記事を参照してください。

D言語の特徴

前回と今回のソースコードを書いた際にあらためて実感した、D言語の特徴を2つ紹介します。

ABI互換

D言語は、C言語とのABI互換があります。
つまり、本来C言語での開発用に作成されたFFmpegSDLのライブラリファイルをD言語のコンパイル時にそのまま利用できます。また、FFmpegSDLAPIについて、C言語向けの解説をD言語でも活用できます。
この記事は、実際の活用例になります。

スレッド局所記憶

D言語では、スレッド局所記憶を採用しています。
D言語のグローバル変数はデフォルトではスレッド局所変数のため、スレッドセーフです。
ただ、今回のソースコードでは、コールバック関数などの別スレッドとグローバル変数を共有するために、__gshared属性を付けています。
スレッド局所にするかどうかをグローバル変数ごとに選択できるのは、いいですよね。

環境

  • OS : Windows 10 64ビット版 (バージョン 1909)
  • D言語コンパイラ : DMD v2.094.0
  • 使用するライブラリ
    • SDL 2.0.12 (bindbc-sdl バージョン 0.19.2)
    • FFMpeg 4.3.1 (ffmpeg4d バージョン 4.1.0)

ソースコード

app.d
import std.stdio;
import std.utf;
import core.stdc.stdio;
import core.sys.windows.windows;

import bindbc.sdl;
import bindbc.sdl.image;
import ffmpeg;

pragma(lib, "User32.lib");
pragma(lib, "Winmm.lib");

__gshared AVFormatContext* format_context;
__gshared AVStream* audio_stream;
__gshared AVStream* video_stream;
__gshared AVCodecContext* codec_audio;
__gshared AVCodecContext* codec_video;

__gshared SDL_Window* window;

__gshared ubyte*[] videoBuf;
__gshared ulong[]  videoPts;
__gshared int width;
__gshared int height;
__gshared bool startPlay = false;

__gshared SwrContext* swr = null;
__gshared ubyte[] audioBuf;
__gshared ubyte[] waveBuf;
__gshared int read_buf;
__gshared int play_buf;

void drawScreen()
{
    if ( videoBuf.length < 1 ){
        return;
    }
    ubyte* buffer = videoBuf[0];
    int w0 = (width + 7) / 8 * 8;
    SDL_Surface* image = SDL_CreateRGBSurfaceWithFormatFrom(
        cast(void *)buffer, w0, height, 24, w0 * 3, SDL_PIXELFORMAT_BGR24);

    SDL_Surface* screen = SDL_GetWindowSurface(window);
    SDL_BlitSurface(image, null, screen, null);
    SDL_UpdateWindowSurface(window);

    SDL_FreeSurface(image);
    av_free(buffer);
    videoBuf = videoBuf[1..$];
    videoPts = videoPts[1..$];
}

void cacheVideo(AVFrame* frame)
{
    if ( width == 0 ){
        width  = frame.width;
        if ( video_stream.sample_aspect_ratio.num > 0 ){
            width = width * video_stream.sample_aspect_ratio.num / video_stream.sample_aspect_ratio.den;
        }
        height = frame.height;
        initWindow(width, height);
    }
    int w0 = (width + 7) / 8 * 8;
    ulong size = avpicture_get_size(AVPixelFormat.AV_PIX_FMT_BGR24, w0, frame.height);
    ubyte* buffer = cast(ubyte*)av_malloc(size);

    AVFrame* pFrameRGB = av_frame_alloc();
    avpicture_fill(cast(AVPicture*)pFrameRGB, buffer, AVPixelFormat.AV_PIX_FMT_BGR24, w0, frame.height);
    SwsContext* pSWSCtx = sws_getContext(
        frame.width, frame.height, cast(AVPixelFormat)frame.format,
              width, frame.height, AVPixelFormat.AV_PIX_FMT_BGR24,
        SWS_BICUBIC, null, null, null);
    sws_scale(pSWSCtx, cast(ubyte**)frame.data, cast(int*)frame.linesize, 0,
        frame.height, cast(ubyte**)pFrameRGB.data, cast(int*)pFrameRGB.linesize);
    sws_freeContext(pSWSCtx);
    av_free(pFrameRGB);

    videoBuf ~= buffer;
    videoPts ~= frame.pts;
}

void decodeVideo(AVPacket *packet)
{
    AVFrame* frame = av_frame_alloc();
    if ( avcodec_send_packet(codec_video, packet) != 0 ){
        writeln("avcodec_send_packet failed");
    }
    while ( avcodec_receive_frame(codec_video, frame) == 0 ){
        cacheVideo(frame);
    }
    if ( packet != null ){
        av_packet_unref(packet);
    }
    av_frame_free(&frame);
}

extern(Windows) nothrow
LRESULT playProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch ( message ){
        case MM_WOM_DONE:
                play_buf++;
            return ( 0 );
        case MM_WOM_OPEN:
            startPlay = true;
            return ( 0 );
        case WM_DESTROY:
            PostQuitMessage(0);
            return ( 0 );
        default:
    }
    return ( DefWindowProc(hwnd, message, wParam, lParam) );
}

void initSwr(AVFrame* frame)
{
    swr = swr_alloc();
    if ( swr == null ){
        writeln("swr_alloc failed");
    }
    av_opt_set_int(swr, "in_channel_layout", frame.channel_layout, 0);
    av_opt_set_int(swr, "out_channel_layout", frame.channel_layout, 0);
    av_opt_set_int(swr, "in_sample_rate", frame.sample_rate, 0);
    av_opt_set_int(swr, "out_sample_rate", frame.sample_rate, 0);
    av_opt_set_sample_fmt(swr, "in_sample_fmt", cast(AVSampleFormat)frame.format, 0);
    av_opt_set_sample_fmt(swr, "out_sample_fmt", AVSampleFormat.AV_SAMPLE_FMT_S16, 0);
    int ret = swr_init(swr);
    if ( ret < 0 ){
        writefln("swr_init failed : %08x", AVERROR(ret));
    }
}

void quitSwr()
{
    swr_free(&swr);
}

const BUFSIZE = 44100 * 4 * 1;      // 1second
__gshared HWAVEOUT   hwaveout;
__gshared WAVEHDR[2] waveheader;

void initWave(AVFrame* frame)
{
    PCMWAVEFORMAT waveformat;
    waveformat.wf.wFormatTag = WAVE_FORMAT_PCM;
    waveformat.wf.nChannels = cast(ushort)frame.channels;
    waveformat.wf.nSamplesPerSec = frame.sample_rate;
    waveformat.wf.nAvgBytesPerSec = frame.sample_rate * 2;
    waveformat.wf.nBlockAlign = cast(ushort)(frame.channels * 2);
    waveformat.wBitsPerSample = 16;

    int ret;
    ret = waveOutOpen(&hwaveout, WAVE_MAPPER, cast(WAVEFORMATEX*)&waveformat, cast(DWORD_PTR)&playProc, 0, CALLBACK_FUNCTION);
    if ( ret != 0 ){
        writefln("waveOutOpen failed : %d", ret);
    }

    waveBuf.length = BUFSIZE * 2;
    for ( int i = 0; i < 2; i++ ){
        with ( waveheader[i] ){
            lpData = (cast(char*)waveBuf.ptr) + (i * BUFSIZE);
            dwBufferLength = BUFSIZE;
            dwFlags = 0;
            dwLoops = 0;
        }
        ret = waveOutPrepareHeader(hwaveout, &waveheader[i], WAVEHDR.sizeof);
        if ( ret != 0 ){
            writefln("waveOutPrepareHeader failed : %d", ret);
        }
    }
}

void playAudio(uint idx1, uint idx2)
{
    uint base = read_buf % 2;
    waveBuf[base * BUFSIZE .. base * BUFSIZE + idx2 - idx1] = audioBuf[idx1 .. idx2];
    int ret;
    ret = waveOutWrite(hwaveout, &waveheader[base], WAVEHDR.sizeof);
    if ( ret != 0 ){
        writefln("waveOutWrite failed : %d", ret);
    }
    read_buf++;
}

void watchAudioCache()
{
    if ( play_buf + 1 == read_buf ){
        if ( audioBuf.length > (read_buf + 1) * BUFSIZE ){
            playAudio(read_buf * BUFSIZE, (read_buf + 1) * BUFSIZE);
        }
    } else
    if ( play_buf == read_buf ){
        if ( audioBuf.length > (read_buf + 1) * BUFSIZE ){
            playAudio(read_buf * BUFSIZE, (read_buf + 1) * BUFSIZE);
        }
    }
}

void cacheAudio(AVFrame* frame)
{
    if ( swr == null ){
        initSwr(frame);
        initWave(frame);
    }
    int swr_buf_len = frame.nb_samples * frame.channels * 2;
    audioBuf.length += swr_buf_len;
    ubyte* swr_bufp = audioBuf.ptr;
    swr_bufp += audioBuf.length - swr_buf_len;

    int ret = swr_convert(swr, &swr_bufp, frame.nb_samples, cast(const ubyte**)frame.extended_data, frame.nb_samples);
    if ( ret < 0 ){
        writefln("swr_convert failed : %d", AVERROR(ret));
    }
}

void decodeAudio(AVPacket *packet)
{
    AVFrame* frame = av_frame_alloc();
    if ( avcodec_send_packet(codec_audio, packet) != 0 ){
        writeln("avcodec_send_packet failed");
    }
    while ( avcodec_receive_frame(codec_audio, frame) == 0 ){
        cacheAudio(frame);
    }
    if ( packet != null ){
        av_packet_unref(packet);
    }
    av_frame_free(&frame);
}

bool readFrame()
{
    AVPacket packet = AVPacket();
    if ( av_read_frame(format_context, &packet) == 0 ){
        if ( packet.stream_index == audio_stream.index ){
            decodeAudio(&packet);
        } else
        if ( packet.stream_index == video_stream.index ){
            decodeVideo(&packet);
        }
    } else {
        // flush decoder
        decodeAudio(null);
        decodeVideo(null);
        return ( true );
    }
    return ( false );
}

int main(string[] args)
{
    if ( args.length < 2 ){
        writeln("Argument required : input file");
        return ( 1 );
    }
    initFFMpeg(args[1]);
    scope(exit) quitFFMpeg();
    initSDL();
    scope(exit) quitSDL();

    bool endRead = false;
    bool endPlay = false;
    ulong time1 = av_gettime_relative();
    while ( !endPlay ){
        ulong time2 = av_gettime_relative();
        if ( !endRead ){
            if ( videoBuf.length < 60 ){    // Whether to wait
                endRead = readFrame();
            }
        }
        watchAudioCache();
        // Event Check
        SDL_Event event;
        while ( SDL_PollEvent(&event) ){
            switch ( event.type ){
                case SDL_KEYDOWN:
                case SDL_QUIT:
                    endPlay = true;
                    continue;
                default:
            }
        }
        if ( !startPlay ){
            time1 = time2;
        } else {
            ulong p = (videoPts.length == 0) ? 0 : videoPts[0];
            ulong vtime = 1_000_000 * p
                * video_stream.time_base.num / video_stream.time_base.den;
            if ( time1 + vtime < time2 ){   // Whether to wait
                drawScreen();
            }
        }
    }
    quitSwr();
    waveOutClose(hwaveout);

    return ( 0 );
}

void initFFMpeg(string fileName)
{
    // FFmpeg initialize
    av_register_all();
    // AVFormatContext initialize
    const char* input_path = toUTFz!(const(char)*)(fileName);
    if ( avformat_open_input(&format_context, input_path, null, null) != 0 ){
        writeln("avformat_open_input failed");
        writefln("%s", fileName);
    }
    // AVStream initialize
    if ( avformat_find_stream_info(format_context, null) < 0 ){
        writeln("avformat_find_stream_info failed");
    }
    for ( int i = 0; i < cast(int)format_context.nb_streams; ++i ){
        if ( format_context.streams[i].codecpar.codec_type == AVMediaType.AVMEDIA_TYPE_AUDIO ){
            audio_stream = format_context.streams[i];
            codec_audio = getCodecContext(audio_stream);
        } else
        if ( format_context.streams[i].codecpar.codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO ){
            video_stream = format_context.streams[i];
            codec_video = getCodecContext(video_stream);
        }
    }
    if ( audio_stream == null ){
        writeln("No audio stream ...");
    }
    if ( video_stream == null ){
        writeln("No video stream ...");
    }
}

AVCodecContext* getCodecContext(AVStream* stream)
{
    AVCodec* codec = avcodec_find_decoder(stream.codecpar.codec_id);
    if ( codec == null ){
        writeln("No supported decoder ...");
    }
    AVCodecContext* codec_context = avcodec_alloc_context3(codec);
    if ( codec_context == null ){
        writeln("avcodec_alloc_context3 failed");
    }
    if ( avcodec_parameters_to_context(codec_context, stream.codecpar) < 0 ){
        writeln("avcodec_parameters_to_context failed");
    }
    if ( avcodec_open2(codec_context, codec, null) != 0 ){
        writeln("avcodec_open2 failed");
    }

    return ( codec_context );
}

void quitFFMpeg()
{
    avcodec_free_context(&codec_audio);
    avcodec_free_context(&codec_video);
    avformat_close_input(&format_context);
}

void initSDL()
{
    // Load SDL
    SDLSupport ret = loadSDL();
    if ( ret != sdlSupport ){
        if ( ret == SDLSupport.noLibrary ){
            writeln("SDLSupport.noLibrary");
        } else if ( SDLSupport.badLibrary ){
            writeln("SDLSupport.badLibrary");
        }
    }
    // Initialize SDL
    if ( SDL_Init(SDL_INIT_VIDEO) < 0 ){
        writefln("SDL_Init failed : %s", SDL_GetError());
    }
    // Load SDLImage
    immutable sdlImageVersion = loadSDLImage();
    IMG_Init(IMG_INIT_JPG | IMG_INIT_PNG);
}

void initWindow(int wid, int hei)
{
    window = SDL_CreateWindow("FFMPEG PLAYER",
        SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
        wid, hei, SDL_WINDOW_SHOWN);
    if ( window == null ){
        writefln("Window could not be created! SDL_Error: %s", SDL_GetError());
    }
}

void quitSDL()
{
    SDL_DestroyWindow(window);
    IMG_Quit();
    unloadSDLImage();
    SDL_Quit();
    unloadSDL();
}
dub.json
{
    "name": "mplayer",
    "description": "video player",
    "authors": [
        "devmynote"
    ],
    "license": "BSL-1.0",
    "dependencies": {
        "ffmpeg4d": "~>4.1.0",
        "bindbc-sdl": "~>0.19.2",
    },
    "versions": ["SDL_205","BindSDL_Image"],
    "lflags":["/libpath:./lib"],
}

ソースコード補足説明

シーク機能やボリューム調整機能などがないシンプルな動画再生処理となります。おかげで400行弱に収まりました。
ffmpegの大まかな使い方を調べるには、行数が少ない分、わかりやすいのではと思います。
グローバル変数だらけのソースコードで、なんかすいません・・・

音楽データも画像データもffmpegを通して、AVFrame単位で取得できるところは共通です。
そのあと、音楽データはWAVE_FORMAT_PCM形式に変換し、Windows APIを使って再生しています。
画像データは、AV_PIX_FMT_BGR24形式に変換し、SDLを使って再生しています。

実装した各関数を簡単に説明します。

関数名 関数の簡単な説明
drawScreen グローバル変数に保存した画像データを画面に表示します。
cacheVideo 引数であるframeの画像データをAV_PIX_FMT_BGR24形式に変換し、グローバル変数に保存します。画像表示と音楽再生の同期をとるためにduration情報を取得します。
decodeVideo packetからframe情報を取得し、後続処理に渡しています。
playProc 音楽再生のコールバック関数です。音楽再生の終了を意味するMM_WOM_DONEメッセージを受け取ると、続きの音楽データを再生するようplay_bufをインクリメントします。
initSwr SwrContextを初期化します。
quitSwr SwrContextを開放します。
initWave 音楽再生用PCMWAVEFORMATを初期化します。
playAudio グローバル変数に保存した音楽データを再生します。
watchAudioCache play_bufの値をみて、音楽再生処理を行います。再生途中で音が途切れないようダブルバッファを実装しています
cacheAudio 引数であるframeの音楽データをWAVE_FORMAT_PCM形式に変換し、グローバル変数に保存します。
decodeAudio packetからframe情報を取得し、後続処理に渡しています。
readFrame packetを取得し、後続処理に渡しています。packet.stream_indexの値をみて、音楽処理、画像処理を振り分けます。
main ファイル名である引数をチェックし、ファイルを読み込みます。画像表示と音楽再生の同期をとります。ウィンドウの制御を行います。
initFFMpeg FFMpegライブラリを初期化します。
getCodecContext FFMpeg APIの定型的な処理をまとめています。
quitFFMpeg FFMpegライブラリを開放します。
initSDL SDLライブラリを初期化します。
initWindow 画面に表示するウィンドウを初期化します。
quitSDL SDLライブラリを開放します。

コンパイル

私の開発環境を例にとると、D:\Devが開発用フォルダです。
アプリケーション作成用にmplayerフォルダを作成しています。

D:\Dev> cd mplayer

mplayer内のフォルダ構成はtreeコマンドの出力通りです。

D:\Dev\mplayer> tree
フォルダー パスの一覧:  ボリューム ****
ボリューム シリアル番号は ****-**** です
D:.
├─lib
└─source

D:\Dev\mplayerフォルダにdub.jsonDLLファイルを配置します。
DLLファイルは、ダウンロードしたSDL2-2.0.12-win32-x64.zipffmpeg-release-full-shared.7zにあります。

コマンドプロンプト
D:\Dev\mplayer> dir /B
avcodec-58.dll
avdevice-58.dll
avfilter-7.dll
avformat-58.dll
avutil-56.dll
dub.json
lib
postproc-55.dll
SDL2.dll
source
swresample-3.dll
swscale-5.dll

D:\Dev\mplayer\libフォルダにffmpeg-release-full-shared.7zのlibファイルを配置します。

コマンドプロンプト
D:\Dev\mplayer> dir /B lib
avcodec.lib
avdevice.lib
avfilter.lib
avformat.lib
avutil.lib
postproc.lib
swresample.lib
swscale.lib

D:\Dev\mplayer\sourceフォルダにapp.dを配置します。

コマンドプロンプト
D:\Dev\mplayer> dir /B source
app.d

各ファイルの配置が完了したら、DUBコマンドでコンパイルを実行します。

コマンドプロンプト
D:\Dev\mplayer> dub build --build=release --arch=x86_64
Performing "release" build using D:\App\Dev\dmd2\windows\bin\dmd.exe for x86_64.
bindbc-loader 0.3.2: target for configuration "noBC" is up to date.
bindbc-sdl 0.19.2: target for configuration "dynamic" is up to date.
mplayer ~master: building configuration "application"...
Linking...
To force a rebuild of up-to-date targets, run again with --force.

実行例

コンパイルが無事終了すると、D:\Dev\mplayerフォルダにmplayer.exeが生成されます。
再生したい動画ファイル名を引数に指定して実行することで、動画再生ができます。

D:\Dev\mplayer> mplayer "再生したい動画ファイル名"

おまけ

今回作成した動画再生処理ソースコードの副産物として、画像表示処理のみ版も作りましたので、おまけで紹介します。
画像処理部分だけが抜粋されているので、その部分だけを絞って見たい方の参考になればと思います。
ウェイト処理を入れていないので、音無し、早送りの動画ビューアのような動きになります。

app.d
import std.stdio;
import std.utf;
import core.stdc.string;

import bindbc.sdl;
import bindbc.sdl.image;
import ffmpeg;

int main(string[] args)
{
    if ( args.length < 2 ){
        writeln("Argument required : input file");
        return ( 1 );
    }
    AVFormatContext* format_context;
    AVStream* video_stream;
    AVCodecContext* codec_context;
    initContext(args[1], &format_context, &video_stream, &codec_context);
    playMedia(format_context, video_stream, codec_context);
    avcodec_free_context(&codec_context);
    avformat_close_input(&format_context);

    return ( 0 );
}

void initContext(string fileName, AVFormatContext** pfctx, AVStream** pstream, AVCodecContext** pcctx)
{
    // FFmpeg initialize
    av_register_all();
    // AVFormatContext initialize
    AVFormatContext* format_context;
    const char* input_path = toUTFz!(const(char)*)(fileName);
    if ( avformat_open_input(&format_context, input_path, null, null) != 0 ){
        writeln("avformat_open_input failed");
        writefln("%s", fileName);
    }
    if ( avformat_find_stream_info(format_context, null) < 0 ){
        writeln("avformat_find_stream_info failed");
    }
    // AVStream initialize
    AVStream* video_stream = null;
    for ( int i = 0; i < cast(int)format_context.nb_streams; ++i ){
        if ( format_context.streams[i].codecpar.codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO ){
            video_stream = format_context.streams[i];
            break;
        }
    }
    if ( video_stream == null ){
        writeln("No video stream ...");
    }
    // AVCodecContext initialize
    AVCodec* codec = avcodec_find_decoder(video_stream.codecpar.codec_id);
    if ( codec == null ){
        writeln("No supported decoder ...");
    }
    AVCodecContext* codec_context = avcodec_alloc_context3(codec);
    if ( codec_context == null ){
        writeln("avcodec_alloc_context3 failed");
    }
    if ( avcodec_parameters_to_context(codec_context, video_stream.codecpar) < 0 ){
        writeln("avcodec_parameters_to_context failed");
    }
    if ( avcodec_open2(codec_context, codec, null) != 0 ){
        writeln("avcodec_open2 failed");
    }
    // return value
    *pfctx   = format_context;
    *pstream = video_stream;
    *pcctx   = codec_context;
}

void playMedia(AVFormatContext* format_context, AVStream* video_stream, AVCodecContext* codec_context)
{
    // Load SDL
    SDLSupport ret = loadSDL();
    if ( ret != sdlSupport ){
        if ( ret == SDLSupport.noLibrary ){
            writeln("SDLSupport.noLibrary");
        } else if ( SDLSupport.badLibrary ){
            writeln("SDLSupport.badLibrary");
        }
    }
    scope(exit) unloadSDL();
    // Initialize SDL
    if ( SDL_Init(SDL_INIT_VIDEO) < 0 ){
        writefln("SDL_Init failed : %s", SDL_GetError());
    }
    scope(exit) SDL_Quit();
    // Load SDLImage
    immutable sdlImageVersion = loadSDLImage();
    IMG_Init(IMG_INIT_JPG | IMG_INIT_PNG);
    scope(exit) unloadSDLImage();

    // Create window
    int scrWidth  = codec_context.width;
    if ( video_stream.sample_aspect_ratio.num > 0 ){
        scrWidth = scrWidth * video_stream.sample_aspect_ratio.num / video_stream.sample_aspect_ratio.den;
    }
    SDL_Window* window = SDL_CreateWindow("Video fast-forward",
        SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
        scrWidth, codec_context.height, SDL_WINDOW_SHOWN);
    if ( window == null ){
        writefln("Window could not be created! SDL_Error: %s", SDL_GetError());
    }
    scope(exit) SDL_DestroyWindow(window);
    // Play video
    AVFrame* frame = av_frame_alloc();
    scope(exit) av_frame_free(&frame);
    AVPacket packet = AVPacket();
    PLAY_LOOP: while ( av_read_frame(format_context, &packet) == 0 ){
        if ( packet.stream_index == video_stream.index ){
            if ( avcodec_send_packet(codec_context, &packet) != 0 ){
                writeln("avcodec_send_packet failed");
            }
            while ( avcodec_receive_frame(codec_context, frame) == 0 ){
                drawWindow(frame, window, scrWidth);
            }
        }
        av_packet_unref(&packet);

        // Event Check
        SDL_Event event;
        while ( SDL_PollEvent(&event) ){
            switch ( event.type ){
                case SDL_KEYDOWN:
                    break PLAY_LOOP;
                case SDL_QUIT:
                    break PLAY_LOOP;
                default:
            }
        }
    }
    // flush decoder
    if ( avcodec_send_packet(codec_context, null) != 0 ){
        writeln("avcodec_send_packet failed");
    }
    while ( avcodec_receive_frame(codec_context, frame) == 0 ){
        drawWindow(frame, window, scrWidth);
    }
}

void drawWindow(AVFrame* frame, SDL_Window* window, int scrWidth)
{
    ubyte* buffer = decodeFrame(frame, scrWidth);

    int w0 = (scrWidth + 7) / 8 * 8;
    SDL_Surface* image = SDL_CreateRGBSurfaceWithFormatFrom(
        cast(void *)buffer, w0, frame.height, 24, w0*3, SDL_PIXELFORMAT_BGR24);

    SDL_Surface* screen = SDL_GetWindowSurface(window);
    SDL_BlitSurface(image, null, screen, null);
    SDL_UpdateWindowSurface(window);

    SDL_FreeSurface(image);

    av_free(buffer);
}

ubyte* decodeFrame(AVFrame* frame, int scrWidth)
{
    int w0 = (scrWidth + 7) / 8 * 8;
    ulong size = avpicture_get_size(AVPixelFormat.AV_PIX_FMT_BGR24, w0, frame.height);
    ubyte* buffer = cast(ubyte*)av_malloc(size);

    AVFrame* pFrameRGB = av_frame_alloc();
    avpicture_fill(cast(AVPicture*)pFrameRGB, buffer, AVPixelFormat.AV_PIX_FMT_BGR24, w0, frame.height);
    SwsContext* pSWSCtx = sws_getContext(
        frame.width, frame.height, cast(AVPixelFormat)frame.format,
           scrWidth, frame.height, AVPixelFormat.AV_PIX_FMT_BGR24,
        SWS_BICUBIC, null, null, null);
    sws_scale(pSWSCtx, cast(ubyte**)frame.data, cast(int*)frame.linesize, 0,
        frame.height, cast(ubyte**)pFrameRGB.data, cast(int*)pFrameRGB.linesize);
    sws_freeContext(pSWSCtx);
    av_free(pFrameRGB);

    return ( buffer );
}
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0