在之前的实现中,我们已经能够获取TS文件中的不同音视频轨道信息,并指定索引进行播放。但这仍不完整,例如,在VLC播放器中可以看到直接切换“节目”的选项,而非切换轨道索引。每个节目都包含其专属的音视频轨道,且可能有多个。例如,节目1可能包含一个视频轨道和两个音频轨道(如中英文音轨)。之前未考虑此结构,导致播放某些文件时出现音画不同步的问题,即画面是节目1的,而声音却是节目2的。
通过查阅资料发现,FFmpeg本身提供了接口用于获取节目信息,包括中文节目名称等。回顾之前获取流信息的代码,nb_streams表示流的总数,同时还有nb_programs和nb_chapters。这里的nb是number的缩写,nb_programs即表示节目数量。我们可以根据此数量逐个取出对应的AVProgram对象,以获取详细信息。
需要特别注意的是,AVProgram对象包含一个stream_index数组,其中存储了当前节目所包含的流在全局AVFormatContext对象流数组中的索引。我们需要遍历此数组,判断每个索引对应的流是音频、视频还是其他类型的数据轨道,并过滤掉不需要的类型。
当获得了每个节目对应的音视频轨道索引后,切换节目就变得简单了。AVFormatContext对象通过av_read_frame读取数据时,会读出所有流的数据包。用户只需根据需要选择解码哪些数据包即可。因此,切换节目时,只需动态设置当前需要解码的流索引,并在处理每个数据包(AVPacket)时,判断其stream_index是否属于当前选中节目的音视频轨道索引集合。实践表明,此方法效果极佳,能够实现节目的瞬间实时切换。


核心功能特点
该播放器基于Qt跨平台应用框架与FFmpeg构建,具备以下与TS流处理相关的关键特性:
- 多解码内核支持:除FFmpeg内核外,还支持VLC、mpv、mdk等多种内核,方便在不同场景下选择最优方案。
- 动态节目/轨道切换:可完整读取TS文件的节目信息,支持在播放前或播放过程中动态切换节目及音视频轨道。
- 智能流识别与管理:自动识别视频旋转角度、分辨率变化,支持共享解码线程以节省网络流量。
- 高性能与低延迟:解码与渲染分离,采用高效的FFmpeg多媒体处理框架进行纯解码,结合QOpenGLWidget进行GPU加速渲染,实现极低延迟播放。
- 强大的兼容性:支持H.264/H.265编码,兼容多种音频格式,并可在Windows、Linux、macOS、Android及多种国产嵌入式Linux系统上运行。
关键代码实现
以下代码展示了如何从AVFormatContext中提取节目信息,以及如何根据音视频轨道列表智能组合节目信息。
// 节目信息结构体
struct ProgramInfo {
int id;
QString name;
QString provider;
QList<int> audioTracks;
QList<int> videoTracks;
// 重载打印输出格式
friend QDebug operator<<(QDebug debug, const ProgramInfo &programInfo) {
QStringList list;
list << QString("节目编号: %1").arg(programInfo.id);
list << QString("节目名称: %1").arg(programInfo.name);
list << QString("节目发布: %1").arg(programInfo.provider);
QStringList audios;
foreach (int track, programInfo.audioTracks) {
audios << QString::number(track);
}
QStringList videos;
foreach (int track, programInfo.videoTracks) {
videos << QString::number(track);
}
list << QString("音频索引: %1").arg(audios.join("|"));
list << QString("视频索引: %1").arg(videos.join("|"));
debug.noquote() << list.join(" ");
return debug;
}
};
// 获取节目信息
QList<ProgramInfo> FFmpegThreadHelper::readProgramInfo(AVFormatContext *formatCtx, QList<int> &audioTracks, QList<int> &videoTracks) {
audioTracks.clear();
videoTracks.clear();
QList<ProgramInfo> programs;
// 获取全局音视频索引集合
for (int i = 0; i < formatCtx->nb_streams; ++i) {
AVMediaType type = FFmpegHelper::getMediaType(formatCtx->streams[i]);
if (type == AVMEDIA_TYPE_AUDIO) {
audioTracks << i;
} else if (type == AVMEDIA_TYPE_VIDEO) {
videoTracks << i;
}
}
// 特殊情况处理:当文件仅声明有1个节目,但实际发现多个视频流时,尝试智能组合
if (formatCtx->nb_programs == 1 && videoTracks.count() > 1) {
TrackHelper::getProgram(programs, audioTracks, videoTracks);
return programs;
}
// 标准流程:遍历 nb_programs 获取节目信息
for (int i = 0; i < formatCtx->nb_programs; ++i) {
ProgramInfo program;
AVProgram *avprogram = formatCtx->programs[i];
program.id = avprogram->id;
// 读取元数据,如节目名称和服务提供商
AVDictionaryEntry *tag = NULL;
while ((tag = av_dict_get(avprogram->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
QString key = tag->key;
QString value = tag->value;
if (key == "service_name") {
program.name = value;
} else if (key == "service_provider") {
program.provider = value;
}
}
// 获取该节目下的所有流索引,并分类为音视频轨道
int number = avprogram->nb_stream_indexes;
for (int j = 0; j < number; ++j) {
int index = avprogram->stream_index[j];
AVMediaType type = FFmpegHelper::getMediaType(formatCtx->streams[index]);
if (type == AVMEDIA_TYPE_AUDIO) {
program.audioTracks << index;
} else if (type == AVMEDIA_TYPE_VIDEO) {
program.videoTracks << index;
}
}
programs << program;
}
return programs;
}
// 工具函数:智能组合节目(用于处理无规范节目信息的文件)
void TrackHelper::getProgram(QList<ProgramInfo> &programs, const QList<int> &audioTracks, const QList<int> &videoTracks) {
int audioCount = audioTracks.count();
int videoCount = videoTracks.count();
int number = audioCount / videoCount;
if (audioCount % videoCount != 0) {
return; // 非整数倍关系,不进行自动组合
}
int index = 0;
programs.clear();
for (int i = 0; i < videoCount; ++i) {
ProgramInfo program;
program.id = i;
program.name = QString("节目%1").arg(i + 1);
program.provider = QString("发布%1").arg(i + 1);
program.videoTracks << videoTracks.at(i);
for (int j = 0; j < number; ++j) {
program.audioTracks << audioTracks.at(index++);
}
programs << program;
}
}
通过上述方法,我们能够精确解析TS容器中的多节目结构,并实现流畅、准确的节目切换功能,为开发专业的流媒体播放器提供了关键技术支撑。
|