はじめに
D言語アドベントカレンダー2020 12日目の記事(今回)です。
5日目の記事(前回)の続編的な内容です。
前回の記事では、音楽再生を実装しました。
今回の記事では、続けて、動画再生を実装します。
開発環境の構築情報は前回と同じため、今回は省略しています。興味がある方は、前回の記事を参照してください。
D言語の特徴
前回と今回のソースコードを書いた際にあらためて実感した、D言語の特徴を2つ紹介します。
ABI互換
D言語は、C言語とのABI互換があります。
つまり、本来C言語での開発用に作成されたFFmpeg
やSDL
のライブラリファイルをD言語のコンパイル時にそのまま利用できます。また、FFmpeg
やSDL
のAPI
について、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)
ソースコード
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();
}
{
"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.json
とDLL
ファイルを配置します。
DLL
ファイルは、ダウンロードしたSDL2-2.0.12-win32-x64.zip
とffmpeg-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 "再生したい動画ファイル名"
おまけ
今回作成した動画再生処理ソースコードの副産物として、画像表示処理のみ版も作りましたので、おまけで紹介します。
画像処理部分だけが抜粋されているので、その部分だけを絞って見たい方の参考になればと思います。
ウェイト処理を入れていないので、音無し、早送りの動画ビューアのような動きになります。
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 );
}