FFmpeg入门详解之67:Qt FFmpeg开发播放器

        

理解播放器的基本框架

熟悉常用的结构体

AVFormatContext

AVCodecContext

AVCodec

AVFrame

AVPacket

AVStream

理解基本的同步原理

开发准备

开发环境

Windows7

QT 5.9.8 + Creator 4.4.1

第三方库

FFMPEG 用来读取码流以及解码

SDL2 用来显示画面

框架

框图如图所示

代码

线程划分

主循环读取数据

音频线程解码并播放声音

视频线程解码并显示视频

文件划分

main.c 做初始化工作,读取码流,分发码流

audio.c 音频解码和声音播放

AudioInit 初始化音频

AudioClose 释放资源

AudioDecodeThread 音频解码和播放线程

AudioPlay 播放声音

AudioPacketPush 写入未解码的音频包

AudioPacketSize 当前音频尚未解码的数据总容量

AudioSetTimeBase 音频的base time(以时钟有关系,比如TS为1/90KHZ,另一常见的为1/1000)

vidoe.c 视频解码和视频播放

VideoInit 初始化视频

VideoClose 释放资源

VideoDecodeThread 视频解码和播放线程

VideoDisplay 显示帧

VideoPacketPush 写入未解码的视频包

VideoPacketSize 当前视频尚未解码的数据总容量

VideoGetFirstFrame 是否已经解出第一帧

VideoSetTimeBase 视频的base time

avpacket_queue.c 音频、视频队列,存储解码前的数据

PacketQueueInit 初始化队列

PacketQueueGetSize 队列中所有packet的数据长度(单位为字节)

PacketQueuePut 插入元素

PacketQueueTake 读取元素

log.c 打印日志

LogInit 初始化Log模块,启动后将在运行目录生成一个log.txt的文件,每次程序重启都会重新产生一个文件并将原来的文件覆盖。

LogDebug、LogInfo、LogError和LogNotice 对应不同级别的打印,但目前只是通过宏是否实现来控制

FunEntry 用在函数入口

FunExit 用在函数出口

clock.c 时钟同步用

AVClockResetTime 重置时钟

AVClockGetCurTime 获取当前时间

AVClockSetTime 设置时间

AVClockEnable 使能时钟

AVClockDisable 禁用时钟

AVClockIsEnable 获取时钟是否使能

 
 

AvPacketQueue

#ifndef AVPACKETQUEUE_H
#define AVPACKETQUEUE_H
#include <QQueue>
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
}
#include "SDL2/SDL.h"
class AvPacketQueue
{
public:
    explicit AvPacketQueue();
    void enqueue(AVPacket *packet);
    void dequeue(AVPacket *packet, bool isBlock);
    bool isEmpty();
    void empty();
    int queueSize();
private:
    SDL_mutex *mutex;
    SDL_cond *cond;
    QQueue<AVPacket> queue;
};
#endif // AVPACKETQUEUE_H
#include "avpacketqueue.h"
AvPacketQueue::AvPacketQueue()
{
    mutex   = SDL_CreateMutex();
    cond    = SDL_CreateCond();
}
void AvPacketQueue::enqueue(AVPacket *packet)
{
    SDL_LockMutex(mutex);
    queue.enqueue(*packet);
    SDL_CondSignal(cond);
    SDL_UnlockMutex(mutex);
}
void AvPacketQueue::dequeue(AVPacket *packet, bool isBlock)
{
    SDL_LockMutex(mutex);
    while (1) {
        if (!queue.isEmpty()) {
            *packet = queue.dequeue();
            break;
        } else if (!isBlock) {
            break;
        } else {
            SDL_CondWait(cond, mutex);
        }
    }
    SDL_UnlockMutex(mutex);
}
void AvPacketQueue::empty()
{
    SDL_LockMutex(mutex);
    while (queue.size() > 0) {
        AVPacket packet = queue.dequeue();
        av_packet_unref(&packet);
    }
    SDL_UnlockMutex(mutex);
}
bool AvPacketQueue::isEmpty()
{
    return queue.isEmpty();
}
int AvPacketQueue::queueSize()
{
    return queue.size();
}

AudioDecoder

#ifndef AUDIODECODER_H
#define AUDIODECODER_H
#include <QObject>
extern "C"
{
    #include "libswresample/swresample.h"
}
#include "avpacketqueue.h"
class AudioDecoder : public QObject
{
    Q_OBJECT
public:
    explicit AudioDecoder(QObject *parent = nullptr);
    int openAudio(AVFormatContext *pFormatCtx, int index);
    void closeAudio();
    void pauseAudio(bool pause);
    void stopAudio();
    int getVolume();
    void setVolume(int volume);
    double getAudioClock();
    void packetEnqueue(AVPacket *packet);
    void emptyAudioData();
    void setTotalTime(qint64 time);
private:
    int decodeAudio();
    static void audioCallback(void *userdata, quint8 *stream, int SDL_AudioBufSize);
    bool isStop;
    bool isPause;
    bool isreadFinished;
    qint64 totalTime;
    double clock;
    int volume;
    AVStream *stream;
    quint8 *audioBuf;
    quint32 audioBufSize;
    DECLARE_ALIGNED(16, quint8, audioBuf1) [192000];
    quint32 audioBufSize1;
    quint32 audioBufIndex;
    SDL_AudioSpec spec;
    quint32 audioDeviceFormat;  // audio device sample format
    quint8 audioDepth;
    struct SwrContext *aCovertCtx;
    qint64 audioDstChannelLayout;
    enum AVSampleFormat audioDstFmt;   // audio decode sample format
    qint64 audioSrcChannelLayout;
    int audioSrcChannels;
    enum AVSampleFormat audioSrcFmt;
    int audioSrcFreq;
    AVCodecContext *codecCtx;          // audio codec context
    AvPacketQueue packetQueue;
    AVPacket packet;
    int sendReturn;
signals:
    void playFinished();
public slots:
    void readFileFinished();
};
#endif // AUDIODECODER_H
#include <QDebug>
#include "audiodecoder.h"
/* Minimum SDL audio buffer size, in samples. */
#define SDL_AUDIO_MIN_BUFFER_SIZE 512
/* Calculate actual buffer size keeping in mind not cause too frequent audio callbacks */
#define SDL_AUDIO_MAX_CALLBACKS_PER_SEC 30
AudioDecoder::AudioDecoder(QObject *parent) :
    QObject(parent),
    isStop(false),
    isPause(false),
    isreadFinished(false),
    totalTime(0),
    clock(0),
    volume(SDL_MIX_MAXVOLUME),
    audioDeviceFormat(AUDIO_F32SYS),
    aCovertCtx(NULL),
    sendReturn(0)
{
}
int AudioDecoder::openAudio(AVFormatContext *pFormatCtx, int index)
{
    AVCodec *codec;
    SDL_AudioSpec wantedSpec;
    int wantedNbChannels;
    const char *env;
    /*  soundtrack array use to adjust */
    int nextNbChannels[]   = {0, 0, 1, 6, 2, 6, 4, 6};
    int nextSampleRates[]  = {0, 44100, 48000, 96000, 192000};
    int nextSampleRateIdx = FF_ARRAY_ELEMS(nextSampleRates) - 1;
    isStop = false;
    isPause = false;
    isreadFinished = false;
    audioSrcFmt = AV_SAMPLE_FMT_NONE;
    audioSrcChannelLayout = 0;
    audioSrcFreq = 0;
    pFormatCtx->streams[index]->discard = AVDISCARD_DEFAULT;
    stream = pFormatCtx->streams[index];
    codecCtx = avcodec_alloc_context3(NULL);
    avcodec_parameters_to_context(codecCtx, pFormatCtx->streams[index]->codecpar);
    /* find audio decoder */
    if ((codec = avcodec_find_decoder(codecCtx->codec_id)) == NULL) {
        avcodec_free_context(&codecCtx);
        qDebug() << "Audio decoder not found.";
        return -1;
    }
    /* open audio decoder */
    if (avcodec_open2(codecCtx, codec, NULL) < 0) {
        avcodec_free_context(&codecCtx);
        qDebug() << "Could not open audio decoder.";
        return -1;
    }
    totalTime = pFormatCtx->duration;
    env = SDL_getenv("SDL_AUDIO_CHANNELS");
    if (env) {
        qDebug() << "SDL audio channels";
        wantedNbChannels = atoi(env);
        audioDstChannelLayout = av_get_default_channel_layout(wantedNbChannels);
    }
    wantedNbChannels = codecCtx->channels;
    if (!audioDstChannelLayout ||
        (wantedNbChannels != av_get_channel_layout_nb_channels(audioDstChannelLayout))) {
        audioDstChannelLayout = av_get_default_channel_layout(wantedNbChannels);
        audioDstChannelLayout &= ~AV_CH_LAYOUT_STEREO_DOWNMIX;
    }
    wantedSpec.channels    = av_get_channel_layout_nb_channels(audioDstChannelLayout);
    wantedSpec.freq        = codecCtx->sample_rate;
    if (wantedSpec.freq <= 0 || wantedSpec.channels <= 0) {
        avcodec_free_context(&codecCtx);
        qDebug() << "Invalid sample rate or channel count, freq: " << wantedSpec.freq << " channels: " << wantedSpec.channels;
        return -1;
    }
    while (nextSampleRateIdx && nextSampleRates[nextSampleRateIdx] >= wantedSpec.freq) {
        nextSampleRateIdx--;
    }
    wantedSpec.format      = audioDeviceFormat;
    wantedSpec.silence     = 0;
    wantedSpec.samples     = FFMAX(SDL_AUDIO_MIN_BUFFER_SIZE, 2 << av_log2(wantedSpec.freq / SDL_AUDIO_MAX_CALLBACKS_PER_SEC));
    //音频回调函数:最重要
    wantedSpec.callback    = &AudioDecoder::audioCallback;
    wantedSpec.userdata    = this;
    /* This function opens the audio device with the desired parameters, placing
     * the actual hardware parameters in the structure pointed to spec.
     */
    while (1) {
        while (SDL_OpenAudio(&wantedSpec, &spec) < 0) {
            qDebug() << QString("SDL_OpenAudio (%1 channels, %2 Hz): %3")
                    .arg(wantedSpec.channels).arg(wantedSpec.freq).arg(SDL_GetError());
            wantedSpec.channels = nextNbChannels[FFMIN(7, wantedSpec.channels)];
            if (!wantedSpec.channels) {
                wantedSpec.freq = nextSampleRates[nextSampleRateIdx--];
                wantedSpec.channels = wantedNbChannels;
                if (!wantedSpec.freq) {
                    avcodec_free_context(&codecCtx);
                    qDebug() << "No more combinations to try, audio open failed";
                    return -1;
                }
            }
            audioDstChannelLayout = av_get_default_channel_layout(wantedSpec.channels);
        }
        if (spec.format != audioDeviceFormat) {
            qDebug() << "SDL audio format: " << wantedSpec.format << " is not supported"
                     << ", set to advised audio format: " <<  spec.format;
            wantedSpec.format = spec.format;
            audioDeviceFormat = spec.format;
            SDL_CloseAudio();
        } else {
            break;
        }
    }
    if (spec.channels != wantedSpec.channels) {
        audioDstChannelLayout = av_get_default_channel_layout(spec.channels);
        if (!audioDstChannelLayout) {
            avcodec_free_context(&codecCtx);
            qDebug() << "SDL advised channel count " << spec.channels << " is not supported!";
            return -1;
        }
    }
    /* set sample format */
    switch (audioDeviceFormat) {
    case AUDIO_U8:
        audioDstFmt    = AV_SAMPLE_FMT_U8;
        audioDepth = 1;
        break;
    case AUDIO_S16SYS:
        audioDstFmt    = AV_SAMPLE_FMT_S16;
        audioDepth = 2;
        break;
    case AUDIO_S32SYS:
        audioDstFmt    = AV_SAMPLE_FMT_S32;
        audioDepth = 4;
        break;
    case AUDIO_F32SYS:
        audioDstFmt    = AV_SAMPLE_FMT_FLT;
        audioDepth = 4;
        break;
    default:
        audioDstFmt    = AV_SAMPLE_FMT_S16;
        audioDepth = 2;
        break;
    }
    /* open sound */
    SDL_PauseAudio(0);
    return 0;
}
void AudioDecoder::closeAudio()
{
    emptyAudioData();
    SDL_LockAudio();
    SDL_CloseAudio();
    SDL_UnlockAudio();
    avcodec_close(codecCtx);
    avcodec_free_context(&codecCtx);
}
void AudioDecoder::readFileFinished()
{
    isreadFinished = true;
}
void AudioDecoder::pauseAudio(bool pause)
{
    isPause = pause;
}
void AudioDecoder::stopAudio()
{
    isStop = true;
}
void AudioDecoder::packetEnqueue(AVPacket *packet)
{
    packetQueue.enqueue(packet);
}
void AudioDecoder::emptyAudioData()
{
    audioBuf = nullptr;
    audioBufIndex = 0;
    audioBufSize = 0;
    audioBufSize1 = 0;
    clock = 0;
    sendReturn = 0;
    packetQueue.empty();
}
int AudioDecoder::getVolume()
{
    return volume;
}
void AudioDecoder::setVolume(int volume)
{
    this->volume = volume;
}
double AudioDecoder::getAudioClock()
{
    if (codecCtx) {
        /* control audio pts according to audio buffer data size */
        int hwBufSize   = audioBufSize - audioBufIndex;
        int bytesPerSec = codecCtx->sample_rate * codecCtx->channels * audioDepth;
        clock -= static_cast<double>(hwBufSize) / bytesPerSec;
    }
    return clock;
}
void AudioDecoder::audioCallback(void *userdata, quint8 *stream, int SDL_AudioBufSize)
{
    AudioDecoder *decoder = (AudioDecoder *)userdata;
    int decodedSize;
    /* SDL_BufSize means audio play buffer left size
     * while it greater than 0, means counld fill data to it
     */
    while (SDL_AudioBufSize > 0) {
        if (decoder->isStop) {
            return ;
        }
        if (decoder->isPause) {
            SDL_Delay(10);
            continue;
        }
        /* no data in buffer */
        if (decoder->audioBufIndex >= decoder->audioBufSize) {
            decodedSize = decoder->decodeAudio();
            /* if error, just output silence */
            if (decodedSize < 0) {
                /* if not decoded data, just output silence */
                decoder->audioBufSize = 1024;
                decoder->audioBuf = nullptr;
            } else {
                decoder->audioBufSize = decodedSize;
            }
            decoder->audioBufIndex = 0;
        }
        /* calculate number of data that haven't play */
        int left = decoder->audioBufSize - decoder->audioBufIndex;
        if (left > SDL_AudioBufSize) {
            left = SDL_AudioBufSize;
        }
        if (decoder->audioBuf) {
            memset(stream, 0, left);
            SDL_MixAudio(stream, decoder->audioBuf + decoder->audioBufIndex, left, decoder->volume);
        }
        SDL_AudioBufSize -= left;
        stream += left;
        decoder->audioBufIndex += left;
    }
}
int AudioDecoder::decodeAudio()
{
    int ret;
    AVFrame *frame = av_frame_alloc();
    int resampledDataSize;
    if (!frame) {
        qDebug() << "Decode audio frame alloc failed.";
        return -1;
    }
    if (isStop) {
        return -1;
    }
    if (packetQueue.queueSize() <= 0) {
        if (isreadFinished) {
            isStop = true;
            SDL_Delay(100);
            emit playFinished();
        }
        return -1;
    }
    /* get new packet whiel last packet all has been resolved */
    if (sendReturn != AVERROR(EAGAIN)) {
        packetQueue.dequeue(&packet, true);
    }
    if (!strcmp((char*)packet.data, "FLUSH")) {
        avcodec_flush_buffers(codecCtx);
        av_packet_unref(&packet);
        av_frame_free(&frame);
        sendReturn = 0;
        qDebug() << "seek audio";
        return -1;
    }
    /* while return -11 means packet have data not resolved,
     * this packet cannot be unref
     */
    sendReturn = avcodec_send_packet(codecCtx, &packet);
    if ((sendReturn < 0) && (sendReturn != AVERROR(EAGAIN)) && (sendReturn != AVERROR_EOF)) {
        av_packet_unref(&packet);
        av_frame_free(&frame);
        qDebug() << "Audio send to decoder failed, error code: " << sendReturn;
        return sendReturn;
    }
    ret = avcodec_receive_frame(codecCtx, frame);
    if ((ret < 0) && (ret != AVERROR(EAGAIN))) {
        av_packet_unref(&packet);
        av_frame_free(&frame);
        qDebug() << "Audio frame decode failed, error code: " << ret;
        return ret;
    }
    if (frame->pts != AV_NOPTS_VALUE) {
        clock = av_q2d(stream->time_base) * frame->pts;
//        qDebug() << "no pts";
    }
    /* get audio channels */
    qint64 inChannelLayout = (frame->channel_layout && frame->channels == av_get_channel_layout_nb_channels(frame->channel_layout)) ?
                frame->channel_layout : av_get_default_channel_layout(frame->channels);
    if (frame->format       != audioSrcFmt              ||
        inChannelLayout     != audioSrcChannelLayout    ||
        frame->sample_rate  != audioSrcFreq             ||
        !aCovertCtx) {
        if (aCovertCtx) {
            swr_free(&aCovertCtx);
        }
        /* init swr audio convert context */
        aCovertCtx = swr_alloc_set_opts(nullptr, audioDstChannelLayout, audioDstFmt, spec.freq,
                inChannelLayout, (AVSampleFormat)frame->format , frame->sample_rate, 0, NULL);
        if (!aCovertCtx || (swr_init(aCovertCtx) < 0)) {
            av_packet_unref(&packet);
            av_frame_free(&frame);
            return -1;
        }
        audioSrcFmt             = (AVSampleFormat)frame->format;
        audioSrcChannelLayout   = inChannelLayout;
        audioSrcFreq            = frame->sample_rate;
        audioSrcChannels        = frame->channels;
    }
    if (aCovertCtx) {
        const quint8 **in   = (const quint8 **)frame->extended_data;
        uint8_t *out[] = {audioBuf1};
        int outCount = sizeof(audioBuf1) / spec.channels / av_get_bytes_per_sample(audioDstFmt);
        int sampleSize = swr_convert(aCovertCtx, out, outCount, in, frame->nb_samples);
        if (sampleSize < 0) {
            ///qDebug() << "swr convert failed";
            av_packet_unref(&packet);
            av_frame_free(&frame);
            return -1;
        }
        if (sampleSize == outCount) {
            qDebug() << "audio buffer is probably too small";
            if (swr_init(aCovertCtx) < 0) {
                swr_free(&aCovertCtx);
            }
        }
        audioBuf = audioBuf1;
        resampledDataSize = sampleSize * spec.channels * av_get_bytes_per_sample(audioDstFmt);
    } else {
        audioBuf = frame->data[0];
        resampledDataSize = av_samples_get_buffer_size(NULL, frame->channels, frame->nb_samples, static_cast<AVSampleFormat>(frame->format), 1);
    }
    clock += static_cast<double>(resampledDataSize) / (audioDepth * codecCtx->channels * codecCtx->sample_rate);
    if (sendReturn != AVERROR(EAGAIN)) {
        av_packet_unref(&packet);
    }
    av_frame_free(&frame);
    return resampledDataSize;
}

MainDecoder

#ifndef DECODER_H
#define DECODER_H
#include <QThread>
#include <QImage>
extern "C"
{
#include "libavfilter/avfiltergraph.h"
#include "libavfilter/buffersink.h"
#include "libavfilter/buffersrc.h"
#include "libswscale/swscale.h"
#include "libavdevice/avdevice.h"
#include "libavutil/pixfmt.h"
#include "libavutil/opt.h"
#include "libavcodec/avfft.h"
#include "libavutil/imgutils.h"
}
#include "audiodecoder.h"
class MainDecoder : public QThread
{
    Q_OBJECT
public:
    enum PlayState {
        STOP,
        PAUSE,
        PLAYING,
        FINISH
    };
    explicit MainDecoder();
    ~MainDecoder();
    double getCurrentTime();
    void seekProgress(qint64 pos);
    int getVolume();
    void setVolume(int volume);
private:
    void run();
    void clearData();
    void setPlayState(MainDecoder::PlayState state);
    void displayVideo(QImage image);
    static int videoThread(void *arg);
    double synchronize(AVFrame *frame, double pts);
    bool isRealtime(AVFormatContext *pFormatCtx);
    int initFilter();
    int fileType;
    int videoIndex;
    int audioIndex;
    int subtitleIndex;
    QString currentFile;
    QString currentType;
    qint64 timeTotal;
    AVPacket seekPacket;
    qint64 seekPos;
    double seekTime;
    PlayState playState;
    bool isStop;
    bool gotStop;
    bool isPause;
    bool isSeek;
    bool isReadFinished;
    bool isDecodeFinished;
    AVFormatContext *pFormatCtx;
    AVCodecContext *pCodecCtx;          // video codec context
    AvPacketQueue videoQueue;
    AvPacketQueue subtitleQueue;
    AVStream *videoStream;
    double videoClk;    // video frame timestamp
    AudioDecoder *audioDecoder;
    AVFilterGraph   *filterGraph;
    AVFilterContext *filterSinkCxt;
    AVFilterContext *filterSrcCxt;
public slots:
    void decoderFile(QString file, QString type);
    void stopVideo();
    void pauseVideo();
    void audioFinished();
signals:
    void readFinished();
    void gotVideo(QImage image);
    void gotVideoTime(qint64 time);
    void playStateChanged(MainDecoder::PlayState state);
};
#endif // DECODER_H

猜你喜欢

转载自blog.csdn.net/teachermei/article/details/127102426