一 概述
本文章实现了对h264/h265裸码流的解码播放功能,主要是一个基于VLC实现的H.264/H.265裸流解码播放类。主要功能包括:1)通过OpenStream接口打开流并指定显示窗口;2)使用InputStream接口输入裸流数据;3)通过CloseStream关闭流。核心实现采用VLC的回调机制(vlc_open_cb/vlc_read_cb/vlc_close_cb)处理流数据,支持自动检测H.264/H.265编码格式。类中还包含了数据缓冲管理、线程安全控制以及调试日志功能,通过配置选项优化了网络缓存和编解码器选择策略,确保稳定播放不同编码格式的视频流。
二 实现接口
-
bool OpenStream(WId winId); // 打开流,传入窗口句柄
-
void CloseStream(); // 关闭流
-
void InputStream(const char* data, int len, string streamType); // 输入裸流数据
三 vlc解码播放类
1.vlcstreamdecoder.h
cpp
#ifndef VLCSTREAMDECODER_H
#define VLCSTREAMDECODER_H
#include <QObject>
#include <vlc/vlc.h>
#include <QByteArray>
#include <QMutex>
#include <mutex>
#include <QMutexLocker>
#include <QtWidgets/QWidget>
#include "basic.h"
class VLCStreamDecoder : public QObject
{
Q_OBJECT
public:
explicit VLCStreamDecoder(QObject *parent = nullptr);
~VLCStreamDecoder();
bool OpenStream(WId winId); // 打开流,传入窗口句柄
void CloseStream(); // 关闭流
void InputStream(const char* data, int len, string streamType); // 输入裸流数据
void InputStreamEx(const char* data,int len);
void configureForBothCodecs();
private:
bool h26xWriteOpen(const char* filename);
void h26xWriteFrame(unsigned char* data, int len);
void h26xWriteClose();
private:
// VLC 回调(修复后签名)
static int vlc_open_cb(void* opaque, void** datap, uint64_t* sizep);
static void vlc_close_cb(void* opaque);
static ssize_t vlc_read_cb(void* opaque, unsigned char* buf, size_t len);
libvlc_instance_t* m_vlcInst;
libvlc_media_t* m_vlcMedia;
libvlc_media_player_t* m_vlcPlayer;
std::vector<uint8_t> m_buffer;
std::mutex m_mutex;
QMutex m_packetMutex;
std::list<QByteArray> m_packetList;
FILE* m_h26x_file = nullptr;
bool m_isPlaying = false;
};
#endif // VLCSTREAMDECODER_H
2.vlcstreamdecoder.cpp
cpp
#include "vlcstreamdecoder.h"
#include <QDebug>
#include <iostream>
using namespace std;
VLCStreamDecoder::VLCStreamDecoder(QObject *parent)
: QObject(parent)
, m_vlcInst(nullptr)
, m_vlcMedia(nullptr)
, m_vlcPlayer(nullptr)
{
const char* vlc_args[] = {
"-I", "dummy",
"--no-audio", // 关闭音频(如果你不需要播放声音,保留;需要声音就删掉)
"--no-video-title-show", // 不显示标题
"--quiet",
"--live-caching=0",
"--network-caching=0",
"--verbose=1", //日志级别
"--file-logging",
"--logfile=vlc_log.txt" //日志文件
};
m_vlcInst = libvlc_new(sizeof(vlc_args)/sizeof(vlc_args[0]), vlc_args);
#ifdef WRITE_FILE
h26xWriteOpen("camera.26x");
#endif
}
VLCStreamDecoder::~VLCStreamDecoder()
{
CloseStream();
if (m_vlcInst) {
libvlc_release(m_vlcInst);
m_vlcInst = nullptr;
}
}
bool VLCStreamDecoder::OpenStream(WId winId)
{
if (!m_vlcInst){
qDebug() << "m_vlcInst is null";
return false;
}
// 修复:使用正确的回调函数
m_vlcMedia = libvlc_media_new_callbacks(
m_vlcInst,
vlc_open_cb,
vlc_read_cb,
nullptr,
vlc_close_cb,
this
);
if (!m_vlcMedia){
qDebug() << "m_vlcMedia is null";
return false;
}
// 尝试使用通用解复用器
#if 0
libvlc_media_add_option(m_vlcMedia, ":demux=h264");
libvlc_media_add_option(m_vlcMedia, ":codec=h264");
libvlc_media_add_option(m_vlcMedia, ":demux=hevc");
libvlc_media_add_option(m_vlcMedia, ":codec=hevc");
#endif
// configureForBothCodecs();
// 设置媒体到播放器
m_vlcPlayer = libvlc_media_player_new_from_media(m_vlcMedia);
if (!m_vlcPlayer)
{
qDebug() << "m_vlcPlayer is null";
return false;
}
// 绑定窗口句柄
#ifdef WIN32
libvlc_media_player_set_hwnd(m_vlcPlayer, (void*)winId);
#else
libvlc_media_player_set_xwindow(m_vlcPlayer, (uint32_t)winId);
#endif
qDebug() << "VLC 打开流成功,窗口句柄:" << winId;
return true;
}
void VLCStreamDecoder::CloseStream()
{
if (m_vlcPlayer) {
libvlc_media_player_stop(m_vlcPlayer);
libvlc_media_player_release(m_vlcPlayer);
m_vlcPlayer = nullptr;
}
if (m_vlcMedia) {
libvlc_media_release(m_vlcMedia);
m_vlcMedia = nullptr;
}
#ifdef WRITE_FILE
h26xWriteClose();
#endif
qDebug() << "VLC 流已关闭";
}
// 喂流接口
void VLCStreamDecoder::InputStream(const char *data, int len,string streamType)
{
// ========================
// 1. 超强防御判断
// ========================
if (!data || len <= 0) return;
if (!m_vlcPlayer) return;
#if 1
if(!m_isPlaying){
if(streamType == "h264"){
libvlc_media_add_option(m_vlcMedia, ":demux=h264");
libvlc_media_add_option(m_vlcMedia, ":codec=h264");
}else if(streamType == "h265"){
libvlc_media_add_option(m_vlcMedia, ":demux=hevc");
libvlc_media_add_option(m_vlcMedia, ":codec=hevc");
}
qDebug() << "设置当前播放器支持解码类型为:" << streamType.data();
libvlc_media_player_set_media(m_vlcPlayer,m_vlcMedia);
libvlc_media_player_play(m_vlcPlayer);
m_isPlaying = true;
}
#endif
#ifdef WRITE_FILE
h26xWriteFrame((unsigned char*)data,len);
#endif
std::lock_guard<std::mutex> lock(m_mutex);
const uint8_t *p = (const uint8_t*)data;
m_buffer.insert(m_buffer.end(), p, p + len);
}
void VLCStreamDecoder::InputStreamEx(const char *data, int len)
{
if(!data || len<=0)
return;
if (len > 30) {
QByteArray ba((const char*)data, 30);
qDebug() << "数据十六进制:" << ba.toHex(' ');
}
QByteArray ba(data,len);
{
QMutexLocker lck(&m_packetMutex);
if(m_packetList.size()>40)
return;
m_packetList.push_back(ba);
}
}
void VLCStreamDecoder::configureForBothCodecs()
{
if (!m_vlcMedia) return;
// 不指定特定的解复用器,让VLC自动检测
// 这样VLC可以根据数据内容自动选择h264或h265解复用器
// libvlc_media_add_option(m_vlcMedia, ":demux=h264"); // 不要强制指定
// 使用通用的原始视频解复用器作为备选
libvlc_media_add_option(m_vlcMedia, ":demux=h26x");
// 网络缓存设置(对于流数据很重要)
libvlc_media_add_option(m_vlcMedia, ":network-caching=300");
// 时钟同步设置
libvlc_media_add_option(m_vlcMedia, ":clock-jitter=0");
libvlc_media_add_option(m_vlcMedia, ":clock-synchro=0");
// 不强制指定解码器,让VLC自动选择可用的解码器
// 这样VLC可以根据媒体内容自动选择h264或h265解码器
// libvlc_media_add_option(m_vlcMedia, ":codec=h264"); // 不要强制指定
// 增加缓冲区大小
libvlc_media_add_option(m_vlcMedia, ":input-buffer-size=16384");
// 禁用硬件加速以避免兼容性问题
libvlc_media_add_option(m_vlcMedia, ":avcodec-hw=none");
// 添加错误处理选项
libvlc_media_add_option(m_vlcMedia, ":ignore-config");
libvlc_media_add_option(m_vlcMedia, ":no-audio"); // 专注于视频解码
// 添加编解码器自动检测选项
libvlc_media_add_option(m_vlcMedia, ":codec=any"); // 允许任何可用的解码器
// 启用格式自动检测
libvlc_media_add_option(m_vlcMedia, ":autodetect-fmts");
qDebug() << "Configured for both H.264 and H.265 codecs";
}
bool VLCStreamDecoder::h26xWriteOpen(const char *filename)
{
if (m_h26x_file != NULL) {
fclose(m_h26x_file);
}
m_h26x_file = fopen(filename, "wb"); // 二进制写入
if (m_h26x_file == NULL) {
perror("fopen failed");
return false;
}
qDebug("[H26x] 已打开文件: %s\n", filename);
return true;
}
void VLCStreamDecoder::h26xWriteFrame(unsigned char *data, int len)
{
if (m_h26x_file == NULL || data == NULL || len <= 0) {
return;
}
// 直接写入一帧
fwrite(data, 1, len, m_h26x_file);
// 立即刷新到磁盘(防止掉电丢失)
fflush(m_h26x_file);
// printf("[H26x] 写入一帧: %d 字节\n", len);
}
void VLCStreamDecoder::h26xWriteClose()
{
if (m_h26x_file != NULL) {
fclose(m_h26x_file);
m_h26x_file = NULL;
qDebug("[H26x] 文件已关闭\n");
}
}
// ==========================
// VLC 回调实现(修复版)
// ==========================
int VLCStreamDecoder::vlc_open_cb(void *opaque, void **datap, uint64_t *sizep)
{
*datap = opaque;
*sizep = 0; // 未知大小
return 0;
}
void VLCStreamDecoder::vlc_close_cb(void *opaque)
{
}
ssize_t VLCStreamDecoder::vlc_read_cb(void *opaque, unsigned char *buf, size_t len)
{
VLCStreamDecoder* self = (VLCStreamDecoder*)opaque;
std::lock_guard<std::mutex> lock(self->m_mutex);
if (self->m_buffer.empty()){
buf[0] = 0;
return 1;
}
unsigned int copyLen = std::min(len, (unsigned long)self->m_buffer.size());
memcpy(buf, self->m_buffer.data(), copyLen);
#if 0
qDebug("vlc_read_cb: asked for %zu bytes, have %zu, return %zu\n",
len, self->m_buffer.size(), copyLen);
#endif
self->m_buffer.erase(self->m_buffer.begin(), self->m_buffer.begin() + copyLen);
return copyLen;
}