对各种音视频文件格式的支持,是一个播放器的基础功能。一般的音视频文件只有1路流,比如音频文件只有1路音频流,视频文件只有1路音频1路视频流,实践过程中发现,还有一种ts格式的文件,可能有多路流,这种格式一般是将多路节目流封装到一个文件中,用户可以根据自己的需要切换不同的节目,比如CCTV1、CCTV2都在一个ts流文件中,用户可以选择切换到CCTV1、也可以选择切换到CCTV2,而且音频流和视频流都是分开的索引,也要切换到对应的流这样看起来是音视频一致的,当然也可以选择切换到不同的音频,有些文件是3路视频流外加6路音频流,同时提供了中英双语的音频流,所以在程序的接口中不能写死设置音视频轨道到一个接口,而是应该分别不同的设置都支持,也方便用户切换多语言的音频轨道。
在ffmpeg解码过程中,通过formatCtx->nb_streams可以获取到具体有多少路流,每一路流是音频还是视频,而且都有对应的索引,当用户需要看哪一路的时候,在解码的代码中AVStream *videoStream = formatCtx->stream[videoIndex];传入对应的索引即可,一般来说都是每次播放的只是一路视频,也可以改成同时解码播放多路视频,相当于同时看多个节目。
void FFmpegHelper::getTracks(AVFormatContext *formatCtx, QList<int> &audioTracks, QList<int> &videoTracks)
{
//获取音视频轨道信息(一般有一个音频或者一个视频/ts节目文件可能有多个)
audioTracks.clear();
videoTracks.clear();
int count = formatCtx->nb_streams;
for (int i = 0; i < count; ++i) {
AVMediaType type = FFmpegHelper::getMediaType(formatCtx->streams[i]);
if (type == AVMEDIA_TYPE_AUDIO) {
audioTracks << i;
} else if (type == AVMEDIA_TYPE_VIDEO) {
videoTracks << i;
}
}
}
bool FFmpegThread::initVideo()
{
//找到视频流索引
videoIndex = av_find_best_stream(formatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (videoIndex < 0) {
//有些没有视频流所以这里不用返回
videoIndex = -1;
debug(0, "无视频流", "");
} else {
//如果手动指定了轨道则取指定的(节目流有多个轨道可以指定某个)
if (videoTrack >= 0 && videoTracks.contains(videoTrack)) {
videoIndex = videoTrack;
}
//取出流获取对应的信息创建解码器
int result = -1;
AVStream *videoStream = formatCtx->stream[videoIndex];
//如果主动设置过旋转角度则将旋转信息设置到流信息中以便保存那边也应用(不需要保存也旋转可以注释)
if (rotate != -1) {
FFmpegHelper::setRotate(videoStream, rotate);
}
//先获取旋转角度(如果有旋转角度则不能用硬件加速)
this->getRotate();
if (rotate != 0) {
hardware = "none";
}
//查找视频解码器(如果上面av_find_best_stream第五个参数传了则这里不需要)
AVCodecID codecID = FFmpegHelper::getCodecId(videoStream);
if (codecID == AV_CODEC_ID_NONE) {
debug(result, "无视解码", "");
return false;
}
//获取默认的解码器
videoCodec = avcodec_find_decoder(codecID);
videoCodecName = videoCodec->name;
//创建视频解码器上下文
videoCodecCtx = avcodec_alloc_context3(NULL);
if (!videoCodecCtx) {
debug(result, "创建视解", "");
return false;
}
result = FFmpegHelper::copyContext(videoCodecCtx, videoStream, false);
if (result < 0) {
debug(result, "视频参数", "");
return false;
}
//初始化硬件加速(也可以叫硬解码/如果当前格式不支持硬解则立即切换到软解码)
if (hardware != "none" && !initHardware()) {
hardware = "none";
videoCodec = avcodec_find_decoder(codecID);
}
if (!videoCodec) {
return false;
}
//设置低延迟和加速解码等参数(设置max_lowres的话很可能画面采用最小的分辨率)
if (!getIsFile()) {
//videoCodecCtx->lowres = videoCodec->max_lowres;
videoCodecCtx->flags |= AV_CODEC_FLAG_LOW_DELAY;
videoCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
videoCodecCtx->flags2 |= AV_CODEC_FLAG2_FAST;
}
//打开视频解码器
result = avcodec_open2(videoCodecCtx, videoCodec, NULL);
if (result < 0) {
debug(result, "打开视解", "");
return false;
}
if (videoCodecCtx->pix_fmt == AV_PIX_FMT_NONE) {
debug(0, "格式为空", "");
return false;
}
//获取分辨率大小
FFmpegHelper::getResolution(videoStream, videoWidth, videoHeight);
//如果没有获取到宽高则返回
if (videoWidth == 0 || videoHeight == 0) {
debug(0, "无分辨率", "");
return false;
}
//记录首帧开始时间和解码器名称
videoFirstPts = videoStream->start_time;
videoCodecName = videoCodec->name;
frameRate = FFmpegHelper::getFrameRate(videoStream, formatName);
QString msg = QString("索引: %1 解码: %2 帧率: %3 宽高: %4x%5 角度: %6").arg(videoIndex).arg(videoCodecName).arg(frameRate).arg(videoWidth).arg(videoHeight).arg(rotate);
debug(0, "视频信息", msg);
}
return true;
}
void MdkThread::setAudioTrack(int audioTrack)
{
this->audioTrack = audioTrack;
if (audioTrack >= 0 && audioTracks.count() > 1 && audioTracks.contains(audioTrack)) {
mdkPlayer->setAudioTrack(audioTrack);
}
}
void MdkThread::setVideoTrack(int videoTrack)
{
this->videoTrack = videoTrack;
if (videoTrack >= 0 && videoTracks.count() > 1 && videoTracks.contains(videoTrack)) {
mdkPlayer->setVideoTrack(videoTrack);
}
}