播放音频步骤
音频参数设置
#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