qt+vlc实现解码h264/h265裸码流播放

一 概述

本文章实现了对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编码格式。类中还包含了数据缓冲管理、线程安全控制以及调试日志功能,通过配置选项优化了网络缓存和编解码器选择策略,确保稳定播放不同编码格式的视频流。

二 实现接口

  1. bool OpenStream(WId winId); // 打开流,传入窗口句柄

  2. void CloseStream(); // 关闭流

  3. 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;
}
相关推荐
用户805533698031 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner1 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz6 天前
QML Hello World 入门示例
qt
xcyxiner9 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner10 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner10 天前
DicomViewer (添加模型类)3
qt
xcyxiner11 天前
DicomViewer (目录调整) 2
qt
xcyxiner11 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00613 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术13 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript