【FFmpeg视频播放器开发】音频重采样类和音频播放类的封装(五)
一、前言
在上一篇中我们只实现了 OpenGl 播放视频,现在我们实现播放音频功能,播放音频首先要实现音频重采样,然后通过 Qt 的 QAudioOutput 类实现播放音频。
二、XResample类的实现(重采样)
新创建个工程,命名为 XPlayer_4。然后我们看下 XDemux 类要实现哪些函数:
class XResample
{
public:
XResample();
~XResample();
// 输出参数和输入参数一致除了采样格式,输出为S16 ,会释放para
virtual bool Open(AVCodecParameters *para,bool isClearPara = false); // 打开
virtual void Close(); // 关闭
// 返回重采样后大小,不管成功与否都释放indata空间
virtual int Resample(AVFrame *indata, unsigned char *data);
int outFormat = 1; // 输出格式:AV_SAMPLE_FMT_S16
protected:
std::mutex m_mutex; // 互斥锁
SwrContext *actx = 0; // 上下文
};
2.1 Open():打开
// 输出参数和输入参数一致除了采样格式,输出为S16
bool XResample::Open(AVCodecParameters *para, bool isClearPara)
{
if (!para) return false;
std::unique_lock<std::mutex> guard(m_mutex);
// 音频重采样 上下文初始化
actx = swr_alloc_set_opts(actx,
av_get_default_channel_layout(2), // 输出格式
(AVSampleFormat)outFormat, // 输出样本格式 1 AV_SAMPLE_FMT_S16
para->sample_rate, // 输出采样率
av_get_default_channel_layout(para->channels), // 输入格式
(AVSampleFormat)para->format,
para->sample_rate,
0, 0
);
if(isClearPara)
avcodec_parameters_free(¶);
int nRet = swr_init(actx);
if (nRet != 0)
{
char buf[1024] = { 0 };
av_strerror(nRet, buf, sizeof(buf) - 1);
cout << "swr_init failed! :" << buf << endl;
return false;
}
//unsigned char *pcm = NULL;
return true;
}
2.2 Resample():返回重采样后大小
// 返回重采样后大小,不管成功与否都释放indata空间
int XResample::Resample(AVFrame *indata, unsigned char *d)
{
if (!indata) return 0;
if (!d)
{
av_frame_free(&indata);
return 0;
}
uint8_t *data[2] = { 0 };
data[0] = d;
int nRet = swr_convert(actx,
data, indata->nb_samples, // 输出
(const uint8_t**)indata->data, indata->nb_samples // 输入
);
if (nRet <= 0)return nRet;
int outSize = nRet * indata->channels * av_get_bytes_per_sample((AVSampleFormat)outFormat);
return outSize;
}
2.3 Close():关闭
void XResample::Close()
{
std::unique_lock<std::mutex> guard(m_mutex);
if (actx)
swr_free(&actx);
}
三、XAudioPlay类的实现(音频播放)
头文件如下:
class XAudioPlay
{
public:
XAudioPlay();
virtual ~XAudioPlay();
static XAudioPlay* Get();
//打开音频播放
virtual bool Open() = 0;
virtual void Close() = 0;
//播放音频
virtual bool Write(const unsigned char *data, int datasize) = 0;
virtual int GetFree() = 0;
int sampleRate = 44100;
int sampleSize = 16;
int channels = 2;
};
上面的成员函数留到派生类 CXAudioPlay 来实现:
class CXAudioPlay : public XAudioPlay
{
public:
QAudioOutput *output = NULL;
QIODevice *io = NULL;
std::mutex mutex;
virtual void Close()
{
mutex.lock();
if (io)
{
io->close();
io = NULL;
}
if (output)
{
output->stop();
delete output;
output = 0;
}
mutex.unlock();
}
virtual bool Open()
{
Close();
QAudioFormat fmt;
fmt.setSampleRate(sampleRate);
fmt.setSampleSize(sampleSize);
fmt.setChannelCount(channels);
fmt.setCodec("audio/pcm");
fmt.setByteOrder(QAudioFormat::LittleEndian);
fmt.setSampleType(QAudioFormat::UnSignedInt);
mutex.lock();
output = new QAudioOutput(fmt);
io = output->start(); //开始播放
mutex.unlock();
if(io)
return true;
return false;
}
virtual bool Write(const unsigned char *data, int datasize)
{
if (!data || datasize <= 0)return false;
mutex.lock();
if (!output || !io)
{
mutex.unlock();
return false;
}
int size = io->write((char *)data, datasize);
mutex.unlock();
if (datasize != size)
return false;
return true;
}
virtual int GetFree()
{
mutex.lock();
if (!output)
{
mutex.unlock();
return 0;
}
int free = output->bytesFree();
mutex.unlock();
return free;
}
};
四、客户端实现
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
// 播放界面
XPlayer w;
w.show();
//=================1、解封装测试====================
XDemux demux;
const char* url = "v1080.mp4";
cout << "demux.Open = " << demux.open(url);
demux.read();
cout << "demux.Open = " << demux.open(url); // open一次的话,很久之后才开始播放
cout << "CopyVPara = " << demux.copyVPara() << endl;
cout << "CopyAPara = " << demux.copyAPara() << endl;
// 初始化openGl窗口
w.video->init(demux.getVideoInfo().width, demux.getVideoInfo().height);
//=================2、解码测试====================
XDecode vdecode;
XDecode adecode;
XResample resample; // 新添加
cout << "vdecode.Open() = " << vdecode.Open(demux.copyVPara()) << endl;
cout << "adecode.Open() = " << adecode.Open(demux.copyAPara()) << endl;
// 【新添加音频】
cout << "resample.Open = " << resample.Open(demux.copyAPara()) << endl;
XAudioPlay::Get()->channels = demux.getVideoInfo().channels;
XAudioPlay::Get()->sampleRate = demux.getVideoInfo().sampleRate;
cout << "XAudioPlay::Get()->Open() = " << XAudioPlay::Get()->Open() << endl;
unsigned char* pcm = new unsigned char[1024 * 1024];
// 开辟异步线程进行解码播放,避免阻塞GUI
auto futureLambda = std::async([&]() {
for (;;)
{
AVPacket* pkt = demux.read();
if (!pkt)
{
// 异步线程退出后,才清空销毁
demux.close();
vdecode.Close();
break;
}
if (demux.isAudio(pkt))
{
adecode.Send(pkt);
AVFrame *frame = adecode.Recv();
int len = resample.Resample(frame, pcm);
//cout << "Audio:" << frame << endl;
cout << "Resample:" << len << " ";
while (len > 0)
{
if (XAudioPlay::Get()->GetFree() >= len)
{
XAudioPlay::Get()->Write(pcm, len);
break;
}
QThread::msleep(1);
}
}
else
{
vdecode.Send(pkt);
AVFrame* frame = vdecode.Recv();
// OpenGl子界面重绘
w.video->myRepaint(frame);
QThread::msleep(40); // 25帧播放
}
}
});
return a.exec();
}
测试发现,部分视频播放流畅,同时音画基本同步,但多数视频播放卡顿,且音画不同步。所以后面重点要实现真正的音视频同步(由于时间原因,还未实现)。
五、代码下载
下载链接:https://github.com/confidentFeng/FFmpeg/tree/master/XPlayer/XPlayer_4