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;
}
相关推荐
xyq20242 小时前
W3C CSS 活动
开发语言
Han_han9193 小时前
案例二:交通工具调度系统(核心:继承 + 多态 + final + 方法重写)
java·开发语言
沐知全栈开发3 小时前
CSS Float(浮动)
开发语言
小张-森林人3 小时前
电子病历文书编辑器 Demo
开发语言·c#
cch89183 小时前
Java vs 汇编:高级与低级的终极对决
java·开发语言·汇编
2301_789015623 小时前
C++:异常
开发语言·c++·异常·异常的处理方式
CVer儿3 小时前
c++接口内部内存分配问题设计
开发语言·c++
如若1233 小时前
ERROR:pdf2zh.converter:‘str‘ object has no attribute ‘choices‘ converter.py:357
java·开发语言·servlet
2301_789015623 小时前
C++:智能指针
c语言·开发语言·汇编·c++·智能指针