LoginSignup
9
10

More than 5 years have passed since last update.

FFmpegのAPIを使う~AACデータを再生する

Posted at

はじめに

AACデータを再生する方法を記載します。
OSはWindows10です。開発環境はVisual Studio2015を使いました。

データフローは次の通りです。
TS file -> demux -> (AAC) -> decode -> (fltp PCM) -> (s16 PCM) -> Speaker out

フローのStepを説明します。

  • FFmpegでTS fileからAACデータを取り出します(demux)。
  • FFmpegでAACデータをdecodeしてfltp PCMデータにします。
  • FFmpegでfltp PCMデータをs16 PCMデータに変換します。
  • Xaudio2でs16 PCMデータをSpeaker outします。

PCMフォーマット

PCMのフォーマットを説明します。

FFmpegでAACをデコードするとAV_SAMPLE_FMT_FLTP(fltp)になります。
1sampleが32bit-floatのplanar形式です。

LRが別々のエリア;L1,L2,...,Ln:R1,R2,...,Rn

Xaudio2ではAV_SAMPLE_FMT_S16(s16)にする必要があります。
1sample 16bit-integerのinterleave形式です。

LRが交互に現れる;L1,R1,L2,R2,...,Ln,Rn

fltpをs16に変換する

swresampleを使ってfltpをs16に変換します。

(snip)
if (swr == NULL) {
    swr = swr_alloc();
    if (swr == NULL) {
        printf("swr_alloc error.\n");
        break;
    }
    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", (AVSampleFormat)frame->format, 0);
    av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
    ret = swr_init(swr);
    if (ret < 0) {
        printf("swr_init error ret=%08x.\n", AVERROR(ret));
        break;
    }
    int buf_size = frame->nb_samples*frame->channels * 2; /* the 2 means S16 */
    swr_buf = new BYTE[buf_size];
    swr_buf_len = buf_size;
}

ret = swr_convert(swr, &swr_buf, frame->nb_samples, (const uint8_t**)frame->extended_data, frame->nb_samples);
if (ret < 0) {
    printf("swr_convert error ret=%08x.\n", AVERROR(ret));
}
(snip)

sample code

Win32 Console アプリケーションを作成します。
FFmpegのAPIを使う~ビルド環境構築を参照してください。
[構成プロパティ]-[リンカー]-[入力]-[追加の依存ファイル]に次を追加します。

avcodec.lib
avdevice.lib
avfilter.lib
avformat.lib
avutil.lib
postproc.lib
swresample.lib
swscale.lib
xaudio2.lib

TS file "test.mpg"をFFmpegでdemuxしてAACデータを取り出します。
取り出したAACデータをデコードしてfltpにします。
fltpをs16に変換します。s16をXaudio2でSpeaker outします。

#include "stdafx.h"
#include <xaudio2.h>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
#include <libswresample/swresample.h>
}

class VoiceCallback : public IXAudio2VoiceCallback
{
public:
    HANDLE event;
    VoiceCallback() : event(CreateEvent(NULL, FALSE, FALSE, NULL)) {}
    ~VoiceCallback() { CloseHandle(event); }
    void STDMETHODCALLTYPE OnStreamEnd() {}
    void STDMETHODCALLTYPE OnVoiceProcessingPassEnd() {}
    void STDMETHODCALLTYPE OnVoiceProcessingPassStart(UINT32 samples) {}
    void STDMETHODCALLTYPE OnBufferEnd(void * context) { SetEvent(event); }
    void STDMETHODCALLTYPE OnBufferStart(void * context) {}
    void STDMETHODCALLTYPE OnLoopEnd(void * context) {}
    void STDMETHODCALLTYPE OnVoiceError(void * context, HRESULT Error) {}
};

int main()
{
    int ret = -1;

    ret = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    if (FAILED(ret)) {
        printf("error CoInitializeEx ret=%d\n", ret);
        return -1;
    }

    IXAudio2 *audio = NULL;
    ret = XAudio2Create(&audio);
    if (FAILED(ret)) {
        printf("error XAudio2Create ret=%d\n", ret);
        return -1;
    }

    IXAudio2MasteringVoice *master = NULL;
    ret = audio->CreateMasteringVoice(&master);
    if (FAILED(ret)) {
        printf("error CreateMasteringVoice ret=%d\n", ret);
        return -1;
    }

    av_register_all();

    const char *filename = "test.mpg";
    AVFormatContext *format_context = NULL;
    ret = avformat_open_input(&format_context, filename, NULL, NULL);
    if (ret < 0) {
        printf("cannot open file. filename=%s, ret=%08x\n", filename, AVERROR(ret));
        return -1;
    }

    ret = avformat_find_stream_info(format_context, NULL);
    if (ret < 0) {
        printf("avformat_find_stream_info error. ret=%08x\n", AVERROR(ret));
        return -1;
    }

    AVStream *audio_stream = NULL;
    for (unsigned int i = 0; i < format_context->nb_streams; i++) {
        if (format_context->streams[i]->codecpar->codec_type == AVMediaType::AVMEDIA_TYPE_AUDIO) {
            audio_stream = format_context->streams[i];
            break;
        }
    }
    if (audio_stream == NULL) {
        printf("stream not found\n");
        return -1;
    }

    AVCodec *codec = avcodec_find_decoder(audio_stream->codecpar->codec_id);
    if (codec == NULL) {
        printf("avcodec_find_decoder codec not found. codec_id=%d\n", audio_stream->codecpar->codec_id);
        return -1;
    }

    AVCodecContext *codec_context = avcodec_alloc_context3(codec);
    if (codec_context == NULL) {
        printf("avcodec_alloc_context3 error.\n");
        return -1;
    }

    ret = avcodec_open2(codec_context, codec, NULL);
    if (ret < 0) {
        printf("avcodec_open2 error. ret=%08x\n", AVERROR(ret));
        return -1;
    }

    AVFrame *frame = av_frame_alloc();
    AVPacket packet;
    int frame_number = 0;
    SwrContext *swr = NULL;
    BYTE* swr_buf = 0;
    int swr_buf_len = 0;
    IXAudio2SourceVoice *voice = NULL;
    bool first_submit = true;
    BYTE** buf = NULL;
    int buf_cnt = 0;
    VoiceCallback callback;
    WAVEFORMATEX format = { 0 };

    while (1) {
        // read ES
        if ((ret = av_read_frame(format_context, &packet)) < 0) {
            printf("av_read_frame eof or error. ret=%08x\n", AVERROR(ret));
            break; // eof or error
        }
        if (packet.stream_index == audio_stream->index) {
            // decode ES
            if ((ret = avcodec_send_packet(codec_context, &packet)) < 0) {
                printf("avcodec_send_packet error. ret=%08x\n", AVERROR(ret));
            }
            if ((ret = avcodec_receive_frame(codec_context, frame)) < 0) {
                if (ret != AVERROR(EAGAIN)) {
                    printf("avcodec_receive_frame error. ret=%08x\n", AVERROR(ret));
                    break;
                }
            }
            else {
                printf("frame_number=%d\n", ++frame_number);
                if (swr == NULL) {
                    swr = swr_alloc();
                    if (swr == NULL) {
                        printf("swr_alloc error.\n");
                        break;
                    }
                    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", (AVSampleFormat)frame->format, 0);
                    av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
                    ret = swr_init(swr);
                    if (ret < 0) {
                        printf("swr_init error ret=%08x.\n", AVERROR(ret));
                        break;
                    }
                    int buf_size = frame->nb_samples*frame->channels * 2; /* the 2 means S16 */
                    swr_buf = new BYTE[buf_size];
                    swr_buf_len = buf_size;
                }

                ret = swr_convert(swr, &swr_buf, frame->nb_samples, (const uint8_t**)frame->extended_data, frame->nb_samples);
                if (ret < 0) {
                    printf("swr_convert error ret=%08x.\n", AVERROR(ret));
                }
                if (voice == NULL) {
                    format.wFormatTag = WAVE_FORMAT_PCM;
                    format.nChannels = frame->channels;
                    format.wBitsPerSample = 16;
                    format.nSamplesPerSec = frame->sample_rate;
                    format.nBlockAlign = format.wBitsPerSample / 8 * format.nChannels;
                    format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
                    ret = audio->CreateSourceVoice(
                        &voice,
                        &format,
                        0,                          // UINT32 Flags = 0,
                        XAUDIO2_DEFAULT_FREQ_RATIO, // float MaxFrequencyRatio = XAUDIO2_DEFAULT_FREQ_RATIO,
                        &callback                   // IXAudio2VoiceCallback *pCallback = NULL,
                    );
                    if (FAILED(ret)) {
                        printf("error CreateSourceVoice ret=%d\n", ret);
                    }
                    voice->Start();
                }
                if (buf == NULL) {
                    buf = new BYTE*[2];
                    buf[0] = new BYTE[swr_buf_len];
                    buf[1] = new BYTE[swr_buf_len];
                }
                memcpy(buf[buf_cnt], swr_buf, swr_buf_len);
                XAUDIO2_BUFFER buffer = { 0 };
                buffer.AudioBytes = swr_buf_len;
                buffer.pAudioData = buf[buf_cnt];
                ret = voice->SubmitSourceBuffer(&buffer);
                if (FAILED(ret)) {
                    printf("error SubmitSourceBuffer ret=%d\n", ret);
                }

                if (first_submit) {
                    first_submit = false;
                }
                else {
                    if (WaitForSingleObject(callback.event, INFINITE) != WAIT_OBJECT_0) {
                        printf("error WaitForSingleObject\n");
                    }
                }
                if (2 <= ++buf_cnt)
                    buf_cnt = 0;
            }
        }
        else {
            // does not audio ES.
        }
        av_packet_unref(&packet);
    }

    if (WaitForSingleObject(callback.event, INFINITE) != WAIT_OBJECT_0) {
        printf("error WaitForSingleObject\n");
    }

    if (voice)
        voice->Stop();

    getchar(); // wait user input

    // omit cleanup for simplify. (free/release etc...)

    return 0;
}

references

Xaudio2を使う
FFmpegのAPIを使う~ビルド環境構築
FFmpegのAPIを使う~AACデコーダ出力フォーマットfltpを調べてみる

9
10
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
9
10