欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

FFmpeg——Windows下,视频播放器4:播放音频、音视频同步

程序员文章站 2022-07-01 17:39:56
...

播放音频步骤

FFmpeg——Windows下,视频播放器4:播放音频、音视频同步

音频参数设置

#include "czyplayer.h"
#include <QtWidgets/QApplication>
#include <XFFmpeg.h>
#include <QAudioOutput>

//视频处理, 要时刻注意内存溢出问题, 前后对应申请、释放

int main(int argc, char *argv[])
{

    QAudioOutput *out;
    QAudioFormat fmt;

    //最好音频源是什么,就还原成什么. ffmpeg转换音频容易失帧
    //设置采样率  
    fmt.setSampleRate(44100);   //一秒钟采集了48000个音频点, 正常CD 44100
    //设置采样大小,一般为8位或16位  
    fmt.setSampleSize(16);      //16位 == 65535   有65535种声音
    //设置通道数  
    fmt.setChannelCount(2);     //双声道
    //设置编码方式  
    fmt.setCodec("audio/pcm");  //支持的格式, pcm没有压缩的格式
    //设置字节序  
    fmt.setByteOrder(QAudioFormat::LittleEndian);   //取默认就行
    //设置样本数据类型  
    fmt.setSampleType(QAudioFormat::UnSignedInt);   //每个样本, 用什么样的数据来存的

    out = new QAudioOutput(fmt);
    QIODevice *ad = out->start();

    QApplication a(argc, argv);
    CzyPlayer w;
    w.show();
    return a.exec();
}

解码音频、音频重采样


int XFFmpeg::Open(const char *path)
{
    Close();

    mutex.lock();       //考虑多线程, 尽量晚的锁,尽量早的释放
    /*char *path = "video.mp4";*/

    int re = avformat_open_input(&ic, path, 0, 0);  // lib: avformat
    if (re != 0)
    {
        mutex.unlock();
        av_strerror(re, errorbuf, sizeof(errorbuf));    // lib: avutil
        printf("open %s failed: %s\n", path, errorbuf);
        return 0;
    }
    totalMs = ((ic->duration / AV_TIME_BASE) * 1000);


    for (int i = 0; i < ic->nb_streams; i++)
    {
        //AVCodecContext: 解码器的值
        AVCodecContext *enc = ic->streams[i]->codec;
        if (enc->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            videoStream = i;                //记录视频流index
            fps = r2d(ic->streams[i]->avg_frame_rate);

            AVCodec *codec = avcodec_find_decoder(enc->codec_id);       
            if (!codec)
            {
                mutex.unlock();
                printf("video code not find! \n");  
                return 0;
            }

            //打开解码器
            int err = avcodec_open2(enc, codec, NULL);
            if (err != 0)
            {
                mutex.unlock();
                char buf[1024] = { 0 };
                av_strerror(err, buf, sizeof(buf));
                printf(buf);
                return 0;
            }
            printf("open codec success!\n");
        }
        //音频解码
        else if (enc->codec_type == AVMEDIA_TYPE_AUDIO) //打开音频解码器,赋值属性
        {
            audioStream = i;
            //根据上下文拿到编解码id,通过该id拿到解码器
            AVCodec *codec = avcodec_find_decoder(enc->codec_id);   //aac、 mp3
            //打开解码器
            if (avcodec_open2(enc, codec, NULL) < 0)
            {
                mutex.unlock();
                return false;
            }
            this->sampleRate = enc->sample_rate;
            this->channel = enc->channels;

            switch (enc->sample_fmt)
            {
            case AV_SAMPLE_FMT_S16:
                this->sampleSize = 16;
                break;
            case AV_SAMPLE_FMT_S32:
                this->sampleSize = 32;
                break;
            }
            printf("audio sample Rate: %d  sample size: %d   channel: %d\n",
                this->sampleRate, this->sampleSize, this->channel);
        }
    }

    mutex.unlock();
    return totalMs;
}


int XFFmpeg::Decode(const AVPacket *pkt)
{
    mutex.lock();
    if (!ic)
    {
        mutex.unlock();
        return NULL;
    }

    if (yuv == NULL)
    {
        yuv = av_frame_alloc();
    }

    if (pcm == NULL)
    {
        pcm = av_frame_alloc();
    }

    AVFrame *frame = yuv;
    if (pkt->stream_index == audioStream)
    {
        frame = pcm;
    }

    int re = avcodec_send_packet(ic->streams[pkt->stream_index]->codec, pkt);
    if (re != 0)
    {
        mutex.unlock();
        return NULL;
    }

    re = avcodec_receive_frame(ic->streams[pkt->stream_index]->codec, frame);
    if (re != 0)
    {
        mutex.unlock();
        return NULL;
    }

    mutex.unlock();

    int p = (frame->pts * r2d(ic->streams[pkt->stream_index]->time_base)) * 1000;   //得到毫秒数
    if (pkt->stream_index == audioStream)
        this->pts = p;

    return p;
}


int XFFmpeg::ToPCM(char *out)
{
    mutex.lock();

    if (!ic || !pcm || !out)
    {
        mutex.unlock();
        return 0;
    }
    AVCodecContext *ctx = ic->streams[audioStream]->codec;
    if (aCtx == NULL)
    {
        //frame->16bit 44100 PCM 统一音频采样格式与采样率
        aCtx = swr_alloc();     //SwrContext *aCtx = NULL;

        swr_alloc_set_opts(aCtx, ctx->channel_layout,
            AV_SAMPLE_FMT_S16,
            ctx->sample_rate, 
            ctx->channels,
            ctx->sample_fmt,
            ctx->sample_rate,
            0, 0
            );
        swr_init(aCtx);
    }

    //重采样
    uint8_t *data[1];
    data[0] = (uint8_t *)out;             //分配的大小
    int len = swr_convert(aCtx, data, 10000,
        (const uint8_t **)pcm->data,
        pcm->nb_samples);

    if (len <= 0)
    {
        mutex.unlock();
        return 0;
    }

    int outsize = av_samples_get_buffer_size(
        NULL, 
        ctx->channels, 
        pcm->nb_samples, 
        AV_SAMPLE_FMT_S16, 
        0);

    mutex.unlock();

    return outsize;
}

视频同步音频

人的肉眼对颜色较不敏感,对音频较敏感。 音频一失帧易察觉。 因此以音频为同步基准。

FFmpeg——Windows下,视频播放器4:播放音频、音视频同步

#include "XVideoThread.h"
#include "XFFmpeg.h"
#include "CzyAudioPlay.h"
#include <list>

using namespace std;
static list<AVPacket> videos;
static int apts = -1;

bool isexit = false;
void XVideoThread::run()
{

    char out[10000] = { 0 };
    while (!isexit)
    {

        if (!XFFmpeg::Get()->isPlay)        //如果处于暂停状态, 不读取
        {
            msleep(50);
            continue;
        }

        while (videos.size() > 0)
        {
            AVPacket pack = videos.front();
            int pts = XFFmpeg::Get()->GetPts(&pack);
            XFFmpeg::Get()->GetPts(&pack);

            printf("apts: %d\n", apts);
            //视频、音频显示时间戳比较. 若视频超前则等待
            if (pts > apts)
            {
                break;
            }
            XFFmpeg::Get()->Decode(&pack);
            av_packet_unref(&pack);
            videos.pop_front();
        }

        int free = CzyAudioPlay::Get()->GetFree();
        if (free < 8000)
        {
            msleep(1);
            continue;
        }

        AVPacket pkt = XFFmpeg::Get()->Read();
        if (pkt.size <= 0)
        {
            msleep(10);     //把cpu释放, 休眠    (防止cpu占用)
            continue;
        }

        if (pkt.stream_index == XFFmpeg::Get()->audioStream)
        {
            apts = XFFmpeg::Get()->Decode(&pkt);
            av_packet_unref(&pkt);
            int len = XFFmpeg::Get()->ToPCM(out);
            CzyAudioPlay::Get()->Write(out, len);
            continue;
        }

        if (pkt.stream_index != XFFmpeg::Get()->videoStream)
        {
            av_packet_unref(&pkt);
            continue;
        }

        videos.push_back(pkt);  //将视频存入队列中

    }
}

XVideoThread::XVideoThread()
{
}


XVideoThread::~XVideoThread()
{
}


源码下载  密码:k6jk