オーディオおよびビデオ出力クラスのカプセル化の記録

例証する

モジュラー設計のアイデアは非常に重要です。これにより、繰り返しの無意味な労力が大幅に削減され、すでに習得した知識に何度も時間を無駄にすることはありません。この記事では、自分でパッケージ化したオーディオとビデオの出力クラスを記録し、クラスを直接インスタンス化し、まず対応するオーディオとビデオのパラメーターを設定してから、インターフェースを呼び出して、デコードされたオーディオとビデオのデータを渡して再生します。パッケージ化クラスは主に Qt + FFmpeg + SDL で設計されています。

ビデオ出力クラス

ビデオ出力は、画像デコード後のデータ レンダリングです。メソッドは一意ではありません.たとえば、FFmpeg でデコードされた videoFrame の既存のフレームは、yuv420P の形式です。

まず、Qt 自体の関数で実現できます。
1. CPU メソッド、videoFrame のフォーマットを RGB32 に変換し、QImage を構築してレンダリング用のインターフェイス スレッドに送信します。 2.
GPU メソッド、QOpenglWidget を使用して直接 videoFrame をレンダリングします。 Qt メソッド
使用する明らかな利点は、2 つのレンダリング メソッドを実現するためにサードパーティ ライブラリを導入する必要がないことです。欠点は、特にCPUモードではコード量が比較的多く、フォーマット変換してインターフェーススレッドに送る必要があることと、コードロジックに違いがあり、2つのモードを柔軟に切り替えるのが不便なことです。 .

そのため、個人的には、クロスプラットフォームで実績のある成熟した SDL ライブラリを使用することを好みます.SDL ライブラリを追加で導入しますが、欠点は完全に解決できます. また、CPU と GPU のレンダリングもサポートしており、モードの切り替えに必要なパラメーターは 1 つだけで、非常に柔軟です。また、SDL のレンダリング パフォーマンスは、Qt の方法よりもわずかに優れているはずです。SDL レンダリング AVFrame については、以前のブログ記事で詳しく説明しています

オーディオ出力クラス

オーディオ出力は、デコードされた pcm データを再生することです。その方法は特殊なものではなく、映像に比べて音声のデータ量ははるかに少なく、処理方法も簡単です。SDL を使用してそれを行うこともできますが、SDL のオーディオは通常、内部コールバック メソッドを介しており、ロジックは少し複雑になります。Qt のオーディオ出力インターフェイスは、より適した同期ロジックに属しています。

カプセル化クラス コード

以下は、カプセル化されたクラス コードです。基本的な環境設定を完了し、コピーして直接使用します。ビデオ出力の例:
1.setShowHandle 再生ハンドルを設定
2.setVideoParm 再生パラメータを設定
3.startDisplay 引き続きビデオ デコード データを入力

1. ヘッダファイル mediaoutput.h

#ifndef MEDIAOUTPUT_H
#define MEDIAOUTPUT_H

#include <QAudioFormat>
#include <QAudioOutput>
#include <QDebug>
#include "ffmpegheader.h"
#include "SDL.h"

//视频输出类 SDL
class VideoOutput
{
    
    
public:
    VideoOutput();
    ~VideoOutput();

    //只显示不负责释放 AVFrame为YUV420P格式
    void startDisplay(AVFrame *videoFrame);
    void stopDisplay();

    //设置渲染窗口句柄
    void setShowHandle(void *id);
    //设置视频宽、高、渲染方式
    bool setVideoParm(int width, int height, bool cpuMode = true);
    //释放
    void release();

private:
    //初始化状态
    bool hasInit;

    //用到的相关变量
    SDL_Window *sdlWindow;
    SDL_Renderer *sdlRender;
    SDL_Texture *sdlTexture;

    //显示的句柄
    void *showId;
};


//音频输出类 Qt自带
class AudioOutput
{
    
    
public:
    AudioOutput();
    ~AudioOutput();

    //只播放不负责释放 AVFrame为解码后音频帧
    void startPlay(AVFrame *audioFrame);

    //设置音频参数 采样率 采样格式 通道数 均为ffmpeg解析到的参数
    bool setAudioParm(int sampleRate, int sampleFmt, int channels);
    //释放
    void release();

private:
    //初始化状态
    bool hasInit;

    //音频参数
    int sampleRate;
    int sampleSize;
    int channels;

    //设备输出
    QAudioOutput *audioOutput;
    QIODevice *audioDevice;

    //重采样
    SwrContext *audioSwrCtx;
    uint8_t *audioData;
};

#endif // MEDIAOUTPUT_H

2. ファイル mediaoutput.cpp を実装します。

#include "mediaoutput.h"

VideoOutput::VideoOutput()
{
    
    
    sdlWindow = nullptr;
    sdlRender = nullptr;
    sdlTexture = nullptr;
    showId = nullptr;

    hasInit = false;
}

VideoOutput::~VideoOutput()
{
    
    
    release();
}

void VideoOutput::startDisplay(AVFrame *videoFrame)
{
    
    
    if(!hasInit)
    {
    
    
        qDebug() << "视频参数未设置";
        return;
    }

    if(videoFrame == nullptr)
    {
    
    
        qDebug() << "视频帧无效";
        return;
    }

    //句柄尺寸变化 那么SDL渲染的窗口也会跟着变化
    SDL_Surface *sdlSurface = SDL_GetWindowSurface(sdlWindow);
    if (!sdlSurface)
    {
    
    
        qDebug() << "SDL_GetWindowSurface fail" << SDL_GetError();
        return;
    }

    //渲染
    int ret = SDL_UpdateYUVTexture(sdlTexture, NULL, videoFrame->data[0], videoFrame->linesize[0], videoFrame->data[1], videoFrame->linesize[1], videoFrame->data[2], videoFrame->linesize[2]);
    if (ret != 0)
        qDebug() << "SDL_UpdateYUVTexture fail" << SDL_GetError();

    ret = SDL_RenderClear(sdlRender);
    if (ret != 0)
        qDebug() << "SDL_RenderClear fail" << SDL_GetError();

    ret = SDL_RenderCopy(sdlRender, sdlTexture, NULL, NULL);
    if (ret != 0)
        qDebug() << "SDL_RenderCopy fail" << SDL_GetError();

    SDL_RenderPresent(sdlRender);
}

void VideoOutput::stopDisplay()
{
    
    
    if(!hasInit)
        return;

    SDL_SetRenderDrawColor(sdlRender, 240, 240, 240, 100);
    SDL_RenderClear(sdlRender);
    SDL_RenderPresent(sdlRender);
}

void VideoOutput::setShowHandle(void *id)
{
    
    
    this->showId = id;
}

bool VideoOutput::setVideoParm(int width, int height, bool cpuMode)
{
    
    
    if(this->showId == nullptr)
    {
    
    
        qDebug() << "video showId is nullptr";
        return false;
    }

    //SDL存在界面伸缩有时画面卡住 查阅资料应该是与SDL内部逻辑处理冲突 多次测试采用重置参数方法更为直接有效
    release();

    //默认cpu软件渲染模式 相比gpu硬件渲染兼容性更高
    SDL_RendererFlags flag;
    if(cpuMode)
        flag = SDL_RENDERER_SOFTWARE;
    else
        flag = SDL_RENDERER_ACCELERATED;

    sdlWindow = SDL_CreateWindowFrom(showId);
    if(!sdlWindow)
    {
    
    
        qDebug() << "SDL_CreateWindowFrom error" << SDL_GetError();
        return false;
    }

    sdlRender = SDL_CreateRenderer(sdlWindow, -1, flag);
    if (!sdlRender)
    {
    
    
        qDebug() << "SDL_CreateRenderer error" << SDL_GetError();
        return false;
    }

    sdlTexture = SDL_CreateTexture(sdlRender, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, width, height);
    if (!sdlTexture)
    {
    
    
        qDebug() << "SDL_CreateRenderer error" << SDL_GetError();
        return false;
    }

    SDL_ShowWindow(sdlWindow);
    hasInit = true;
    return true;
}

void VideoOutput::release()
{
    
    
    if(sdlTexture)
    {
    
    
        SDL_DestroyTexture(sdlTexture);
        sdlTexture = nullptr;
    }

    if(sdlRender)
    {
    
    
        SDL_DestroyRenderer(sdlRender);
        sdlRender = nullptr;
    }

    if(sdlWindow)
    {
    
    
        SDL_DestroyWindow(sdlWindow);
        sdlWindow = nullptr;
    }

    hasInit = false;
}


AudioOutput::AudioOutput()
{
    
    
    audioOutput = nullptr;
    audioDevice = nullptr;
    audioSwrCtx = nullptr;
    audioData = nullptr;
    sampleRate = 0;
    sampleSize = 0;
    channels = 0;
    hasInit = false;
}

AudioOutput::~AudioOutput()
{
    
    
    release();
}

void AudioOutput::startPlay(AVFrame *audioFrame)
{
    
    
    if(!hasInit)
    {
    
    
        qDebug() << "音频参数未设置";
        return;
    }

    if(audioFrame == nullptr)
    {
    
    
        qDebug() << "音频帧无效";
        return;
    }

    int outSize = av_samples_get_buffer_size(NULL, channels, audioFrame->nb_samples, AV_SAMPLE_FMT_S16, 1);

    int result = swr_convert(audioSwrCtx, &audioData, outSize, (const uint8_t **)audioFrame->data, audioFrame->nb_samples);
    if(result >= 0)
        audioDevice->write((char *)audioData, outSize);

}

bool AudioOutput::setAudioParm(int sampleRate, int sampleFmt, int channels)
{
    
    
    //释放上次重置即可
    release();

    this->sampleRate = sampleRate;
    this->sampleSize = av_get_bytes_per_sample((AVSampleFormat)sampleFmt) / channels;
    this->channels = channels;

    QAudioFormat format;
    format.setCodec("audio/pcm");
    format.setSampleRate(sampleRate);
    format.setSampleSize(sampleSize * 8);
    format.setChannelCount(channels);
    format.setSampleType(QAudioFormat::SignedInt);
    format.setByteOrder(QAudioFormat::LittleEndian);

    QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());

    bool res = info.isFormatSupported(format);
    if(!res)
    {
    
    
        qDebug() << "AudioFormat not Support";
        return false;
    }

    audioOutput = new QAudioOutput(QAudioDeviceInfo::defaultOutputDevice(), format);
    //设置缓存避免播放音频卡顿  太大可能会分配内存失败导致崩溃
    audioOutput->setBufferSize(40960);
    audioOutput->setVolume(1.0);
    audioDevice = audioOutput->start();

    //设置音频转换
    audioSwrCtx = swr_alloc();
    int64_t channelIn = av_get_default_channel_layout(channels);
    int64_t channelOut = channelIn;
    audioSwrCtx = swr_alloc_set_opts(audioSwrCtx, channelOut, AV_SAMPLE_FMT_S16, sampleRate, channelIn, (AVSampleFormat)sampleFmt, sampleRate, 0, 0);
    if(0 > swr_init(audioSwrCtx))
    {
    
    
        qDebug() << "audio swr_init error";
        release();
        return false;
    }

    //分配音频数据内存19200 可调节
    audioData = (uint8_t *)av_malloc(19200 * sizeof(uint8_t));

    hasInit = true;
    return true;
}

void AudioOutput::release()
{
    
    
    if(audioDevice)
        audioDevice->close();
    if(audioSwrCtx)
        swr_free(&audioSwrCtx);
    if(audioData)
        av_free(audioData);
    if(audioOutput)
        audioOutput->deleteLater();

    hasInit = false;
}

おすすめ

転載: blog.csdn.net/T__zxt/article/details/129789488