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;
}

视频同步音频

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

这里写图片描述

#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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值