30_音视频播放器_解封装
2022-11-15 本文已影响0人
咸鱼Jay
一、简介
我们使用QT+ffmpeg实现一个播放器,这里我们主要是为了学习ffmpege了,而QT只是辅助的,所以播放器的界面搭建我们不在介绍,可以直接看代码(界面搭建代码)。
现在我们直接接入主题,ffmpeg的解封装我们可以直接参考之前介绍的 FFmpeg音视频解封装格式
下面是使用FFmpeg实现音视频播放器的流程图
FFmpeg音视频播放流程图
二、读出文件
在 VideoPlayer
类里的play
方法里实现文件的读取
void VideoPlayer::play() {
if (_state == Playing) return;
// 状态可能是:暂停、停止、正常完毕
// 开始线程:读取文件
std::thread([this](){
readFile();
}).detach();// detach 等到readFile方法执行完,这个线程就会销毁
setState(Playing);
}
我们创建一个线程,在线程里做读取文件的操作,线程thread
调用detach
方法表示等到readFile
方法执行完,这个线程就会销毁
三、初始化
3.1 读取文件
这里实现读取文件的方法,里面主要是读取文件
void VideoPlayer::readFile(){
// 返回结果
int ret = 0;
// 创建解封装上下文、打开文件
ret = avformat_open_input(&_fmtCtx,_filename,nullptr,nullptr);
END(avformat_open_input);
// 检索流信息
ret = avformat_find_stream_info(_fmtCtx,nullptr);
END(avformat_find_stream_info);
// 打印流信息到控制台
av_dump_format(_fmtCtx,0,_filename,0);
fflush(stderr);
// 初始化音频信息
if (initAudioInfo() < 0) {
goto end;
}
// 初始化视频信息
if (initVideoInfo() < 0) {
goto end;
}
// 到此为止,初始化完毕
emit initFinished(this);
// 从输入文件中读取数据
// while (av_read_frame(_fmtCtx,pkt) == 0) {
// if (pkt->stream_index == _aStream->index) { // 读取到的是音频数据
// }else if(pkt->stream_index == _vStream->index){// 读取到的是视频数据
// }
// // 释放pkt内部指针指向的一些额外内存
// av_packet_unref(pkt);
// if(ret < 0){
// goto end;
// }
// }
end:
avcodec_free_context(&_aDecodeCtx);
avcodec_free_context(&_vDecodeCtx);
avformat_close_input(&_fmtCtx);
}
3.2 初始化音频信息和视频信息
// 初始化音频信息
int VideoPlayer::initAudioInfo() {
int ret = initDecoder(&_aDecodeCtx,&_aStream,AVMEDIA_TYPE_AUDIO);
RET(initDecoder);
return 0;
}
// 初始化视频信息
int VideoPlayer::initVideoInfo() {
int ret = initDecoder(&_vDecodeCtx,&_vStream,AVMEDIA_TYPE_VIDEO);
RET(initDecoder);
return 0;
}
3.3 初始化解码器
int VideoPlayer::initDecoder(AVCodecContext **decodeCtx,
AVStream **stream,
AVMediaType type) {
// 根据type寻找最合适的流信息
// 返回值是流索引
int ret = av_find_best_stream(_fmtCtx, type, -1, -1, nullptr, 0);
RET(av_find_best_stream);
// 检验流
int streamIdx = ret;
*stream = _fmtCtx->streams[streamIdx];
if (!*stream) {
qDebug() << "stream is empty";
return -1;
}
// 为当前流找到合适的解码器
AVCodec *decoder = avcodec_find_decoder((*stream)->codecpar->codec_id);
if (!decoder) {
qDebug() << "decoder not found" << (*stream)->codecpar->codec_id;
return -1;
}
// 初始化解码上下文
*decodeCtx = avcodec_alloc_context3(decoder);
if (!decodeCtx) {
qDebug() << "avcodec_alloc_context3 error";
return -1;
}
// 从流中拷贝参数到解码上下文中
ret = avcodec_parameters_to_context(*decodeCtx, (*stream)->codecpar);
RET(avcodec_parameters_to_context);
// 打开解码器
ret = avcodec_open2(*decodeCtx, decoder, nullptr);
RET(avcodec_open2);
return 0;
}
四、实现视频时长
上面我们已经进行了解码器的初始化,所以可以通过AVFormatContext
来获取时长。
在VideoPlayer
类里提供getDuration
方法,用于返回时长
int64_t VideoPlayer::getDuration(){
return _fmtCtx ? _fmtCtx->duration : 0;
}
我们在上面进行初始化后会调用emit initFinished(this);
用于回调MainWindow
类的onPlayerInitFinished
方法,在这个方法里可以更新界面的时长显示
void MainWindow::onPlayerInitFinished(VideoPlayer *player) {
int64_t duration = player->getDuration();
qDebug()<< duration;
// 设置一些slider的范围
ui->currentSlider->setRange(0,duration);
// 设置label的文字
ui->durationLabel->setText(getTimeText(duration));
}
因为getDuration
方法返回的是微妙的时间戳,所以这里需要进行转换成时钟,好进行显示。
QString MainWindow::getTimeText(int64_t value){
int64_t seconds = value / 1000000;
// int64_t timeUs = player->getDuration();
// int h = seconds / 3600;
// int m = (seconds % 3600) / 60;
// int m = (seconds / 60) % 60;
// int s = seconds % 60;
// int ms = timeUs / 1000 % 1000;//微妙
QString h = QString("0%1").arg(seconds / 3600).right(2);
QString m = QString("0%1").arg((seconds / 60) % 60).right(2);
QString s = QString("0%1").arg(seconds % 60).right(2);
QString ms = QString("%1").arg(value / 1000 % 1000);
qDebug()<< h<<m<<s<<ms;
return QString("%1:%2:%3").arg(h).arg(m).arg(s);
}