基于ffmpeg实现一个SimpleStreamClient拉流库

一 简介

本文章主要是使用CLion开发环境实现了一个c/c++媒体流拉流库,该库支持rtsp地址拉流,支持本地mp4文件拉流,支持USB 相机采集拉流。本库可以做为rtsp服务的依赖库,实现一个rtsp服务工具。

二 对外接口

1.初始化媒体流

复制代码
        Simple_API int Simple_APICALL SimpleStreamClient_Init(Simple_Handle *handle, int loglevel);

2.释放媒体流

复制代码
        Simple_API int Simple_APICALL SimpleStreamClient_Deinit(Simple_Handle handle);

3.设置数据回调

复制代码
        Simple_API int Simple_APICALL SimpleStreamClient_SetCallback(Simple_Handle handle, SimpleStreamClientCallBack callback);

4.打开媒体流

复制代码
        Simple_API int Simple_APICALL SimpleStreamClient_OpenStream(Simple_Handle handle, char *url, SIMPLE_RTP_CONNECT_TYPE connType, void *userPtr, int reconn, int timeout, int useExtraData);

5.获取SPS/PPS数据

复制代码
        Simple_API int Simple_APICALL SimpleStreamClient_GetVideoExtraData(Simple_Handle handle,
                                                                    uint8_t* outData, int maxLen);

三 主要代码

SimpleTypes.h

cpp 复制代码
#ifndef SIMPLESTREAMCLIENT_SIMPLETYPES_H
#define SIMPLESTREAMCLIENT_SIMPLETYPES_H

#ifdef _WIN32
#define Simple_API  __declspec(dllexport)
#define Simple_APICALL  __stdcall
#define WIN32_LEAN_AND_MEAN
#else
#define Simple_API __attribute__ ((visibility("default")))
#define Simple_APICALL  __attribute__ ((visibility("default")))
#endif
#define Simple_Handle void*

typedef int						Simple_I32;
typedef unsigned char           Simple_U8;
typedef unsigned char           Simple_UChar;
typedef unsigned short          Simple_U16;
typedef unsigned int            Simple_U32;
typedef unsigned char			Simple_Bool;

enum
{
    Simple_NoErr						= 0,
    Simple_RequestFailed				= -1,
    Simple_Unimplemented				= -2,
    Simple_RequestArrived				= -3,
    Simple_OutOfState					= -4,
    Simple_NotAModule					= -5,
    Simple_WrongVersion				= -6,
    Simple_IllegalService				= -7,
    Simple_BadIndex					= -8,
    Simple_ValueNotFound				= -9,
    Simple_BadArgument				= -10,
    Simple_ReadOnly					= -11,
	Simple_NotPreemptiveSafe			= -12,
    Simple_NotEnoughSpace				= -13,
    Simple_WouldBlock					= -14,
    Simple_NotConnected				= -15,
    Simple_FileNotFound				= -16,
    Simple_NoMoreData					= -17,
    Simple_AttrDoesntExist			= -18,
    Simple_AttrNameExists				= -19,
    Simple_InstanceAttrsNotAllowed	= -20,
	Simple_InvalidSocket				= -21,
	Simple_MallocError				= -22,
	Simple_ConnectError				= -23,
	Simple_SendError					= -24
};
typedef int Simple_Error;

typedef enum __Simple_ACTIVATE_ERR_CODE_ENUM
{
	Simple_ACTIVATE_INVALID_KEY		=		-1,			/* 无效Key */
	Simple_ACTIVATE_TIME_ERR			=		-2,			/* 时间错误 */
	Simple_ACTIVATE_PROCESS_NAME_LEN_ERR	=	-3,			/* 进程名称长度不匹配 */
	Simple_ACTIVATE_PROCESS_NAME_ERR	=		-4,			/* 进程名称不匹配 */
	Simple_ACTIVATE_VALIDITY_PERIOD_ERR=		-5,			/* 有效期校验不一致 */
	Simple_ACTIVATE_PLATFORM_ERR		=		-6,			/* 平台不匹配 */
	Simple_ACTIVATE_COMPANY_ID_LEN_ERR=		-7,			/* 授权使用商不匹配 */
	Simple_ACTIVATE_SUCCESS			=		9999,		/* 激活成功 */

}Simple_ACTIVATE_ERR_CODE_ENUM;

/* 视频编码 */
#define Simple_SDK_VIDEO_CODEC_H264	0x1C		/* H264  */
#define Simple_SDK_VIDEO_CODEC_H265	0xAE	  /* H265 */
#define	Simple_SDK_VIDEO_CODEC_MJPEG	0x08		/* MJPEG */
#define	Simple_SDK_VIDEO_CODEC_MPEG4	0x0D		/* MPEG4 */

/* 音频编码 */
#define Simple_SDK_AUDIO_CODEC_AAC	0x15002		/* AAC */
#define Simple_SDK_AUDIO_CODEC_G711U	0x10006		/* G711 ulaw*/
#define Simple_SDK_AUDIO_CODEC_G711A	0x10007		/* G711 alaw*/
#define Simple_SDK_AUDIO_CODEC_G726	0x1100B		/* G726 */

#define Simple_SDK_EVENT_CODEC_ERROR	0x63657272	/* ERROR */
#define Simple_SDK_EVENT_CODEC_EXIT	0x65786974	/* EXIT */

/* 音视频帧标识 */
#define Simple_SDK_VIDEO_FRAME_FLAG	0x00000001		/* 视频帧标志 */
#define Simple_SDK_AUDIO_FRAME_FLAG	0x00000002		/* 音频帧标志 */
#define Simple_SDK_EVENT_FRAME_FLAG	0x00000004		/* 事件帧标志 */
#define Simple_SDK_RTP_FRAME_FLAG		0x00000008		/* RTP帧标志 */
#define Simple_SDK_SDP_FRAME_FLAG		0x00000010		/* SDP帧标志 */
#define Simple_SDK_MEDIA_INFO_FLAG	0x00000020		/* 媒体类型标志*/
#define Simple_SDK_SNAP_FRAME_FLAG	0x00000040		/* 图片标志*/

/* 视频关键字标识 */
#define Simple_SDK_VIDEO_FRAME_I		0x01		/* I帧 */
#define Simple_SDK_VIDEO_FRAME_P		0x02		/* P帧 */
#define Simple_SDK_VIDEO_FRAME_B		0x03		/* B帧 */
#define Simple_SDK_VIDEO_FRAME_J		0x04		/* JPEG */

/* 连接类型 */
typedef enum __Simple_RTP_CONNECT_TYPE
{
	Simple_RTP_OVER_TCP	=	0x01,		/* RTP Over TCP */
	Simple_RTP_OVER_UDP,					/* RTP Over UDP */
	Simple_RTP_OVER_MULTICAST				/* RTP Over MULTICAST */
}SIMPLE_RTP_CONNECT_TYPE;

typedef struct __Simple_AV_Frame
{
	Simple_U32    u32AVFrameFlag;		/* 帧标志  视频 or 音频 */
	Simple_U32    u32AVFrameLen;		/* 帧的长度 */
	Simple_U32    u32VFrameType;		/* 视频的类型,I帧或P帧 */
	Simple_U8     *pBuffer;			/* 数据 */
	Simple_U32	u32TimestampSec;	/* 时间戳(秒)*/
	Simple_U32	u32TimestampUsec;	/* 时间戳(微秒) */
	Simple_U32    u32PTS;
} Simple_AV_Frame;

/* 媒体信息 */
typedef struct __Simple_MEDIA_INFO_T
{
	Simple_U32 u32VideoCodec;				/* 视频编码类型 */
	Simple_U32 u32VideoFps;				/* 视频帧率 */

	Simple_U32 u32AudioCodec;				/* 音频编码类型 */
	Simple_U32 u32AudioSamplerate;		/* 音频采样率 */
	Simple_U32 u32AudioChannel;			/* 音频通道数 */
	Simple_U32 u32AudioBitsPerSample;		/* 音频采样精度 */

	Simple_U32 u32VpsLength;
	Simple_U32 u32SpsLength;
	Simple_U32 u32PpsLength;
	Simple_U32 u32SeiLength;
	Simple_U8	 u8Vps[256];
	Simple_U8	 u8Sps[256];
	Simple_U8	 u8Pps[128];
	Simple_U8	 u8Sei[128];
}SIMPLE_MEDIA_INFO_T;

/* 帧信息 */
typedef struct
{
	unsigned int	codec;				/* 音视频格式 */

	unsigned int	type;				/* 视频帧类型 */
	unsigned char	fps;				/* 视频帧率 */
	unsigned short	width;				/* 视频宽 */
	unsigned short  height;				/* 视频高 */

	unsigned int	reserved1;			/* 保留参数1 */
	unsigned int	reserved2;			/* 保留参数2 */
	unsigned int	reserved3;			/* 保留参数3 */

	unsigned int	sample_rate;		/* 音频采样率 */
	unsigned int	channels;			/* 音频声道数 */
	unsigned int	bits_per_sample;	/* 音频采样精度 */

	unsigned int	length;				/* 音视频帧大小 */
	unsigned int    timestamp_usec;		/* 时间戳,微妙 */
	unsigned int	timestamp_sec;		/* 时间戳 秒 */
	unsigned int	pts;
	unsigned int    dts;

	float			bitrate;			/* 比特率 */
	float			losspacket;			/* 丢包率 */
}SIMPLE_FRAME_INFO;



#endif //SIMPLESTREAMCLIENT_SIMPLETYPES_H

SimpleStreamClientAPI.h

cpp 复制代码
#ifndef SIMPLESTREAMCLIENT_LIBRARY_H
#define SIMPLESTREAMCLIENT_LIBRARY_H
#include <cstdint>

#include "SimpleTypes.h"

typedef int (Simple_APICALL *SimpleStreamClientCallBack)(void *_channelPtr, int _frameType, void *pBuf, SIMPLE_FRAME_INFO* _frameInfo);

#ifdef __cplusplus
extern "C"
{
#endif
/* 创建SimpleStreamClient句柄  返回0表示成功 ,返回非0表示失败 ;  loglevel : 0 - quiet  1 - debug*/
Simple_API int Simple_APICALL SimpleStreamClient_Init(Simple_Handle *handle, int loglevel);

/* 释放 SimpleStreamClient 参数为SimpleStreamClient句柄 */
Simple_API int Simple_APICALL SimpleStreamClient_Deinit(Simple_Handle handle);

/* 设置数据回调 */
Simple_API int Simple_APICALL SimpleStreamClient_SetCallback(Simple_Handle handle, SimpleStreamClientCallBack callback);

/* 打开网络流 */
Simple_API int Simple_APICALL SimpleStreamClient_OpenStream(Simple_Handle handle, char *url, SIMPLE_RTP_CONNECT_TYPE connType, void *userPtr, int reconn, int timeout, int useExtraData);
    // ===================== 新增:获取 SPS/PPS 等 extradata 接口 =====================
    /**
     * @brief 获取视频 SPS/PPS 数据
     * @param handle 流句柄
     * @param outData 输出缓冲区
     * @param maxLen 缓冲区最大长度
     * @return 实际拷贝长度,<0 失败
     */
    Simple_API int Simple_APICALL SimpleStreamClient_GetVideoExtraData(Simple_Handle handle,
                                                                        uint8_t* outData, int maxLen);
#ifdef __cplusplus
}
#endif

#endif // SIMPLESTREAMCLIENT_LIBRARY_H

BaseType.h

cpp 复制代码
#ifndef SIMPLESTREAMCLIENT_BASETYPE_H
#define SIMPLESTREAMCLIENT_BASETYPE_H
#include <atomic>

#include "SimpleTypes.h"
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/time.h>
#include <libavdevice/avdevice.h>   // 设备采集(USB摄像头)
#ifdef __cplusplus
}
#endif
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
typedef int						Easy_I32;
typedef unsigned char           Easy_U8;
typedef unsigned char           Easy_UChar;
typedef unsigned short          Easy_U16;
typedef unsigned int            Easy_U32;
typedef unsigned char			Easy_Bool;
/* 媒体信息 */
typedef struct __EASY_RTSPSERVER_MEDIA_INFO_T
{
    Easy_U32 videoCodec;				//视频编码类型
    Easy_U32 videoFps;					//视频帧率
    Easy_U32 videoQueueSize;			//视频队列大小	如: 1024 * 1024

    Easy_U32 audioCodec;				//音频编码类型
    Easy_U32 audioSampleRate;			//音频采样率
    Easy_U32 audioChannel;				//音频通道数
    Easy_U32 audioBitsPerSample;		//音频采样精度
    Easy_U32 audioQueueSize;			//音频队列大小	如: 1024 * 128

    Easy_U32 metadataCodec;				//Metadata类型
    Easy_U32 metadataQueueSize;			//Metadata队列大小	如: 1024 * 512

    Easy_U32 vpsLength;				//视频vps帧长度
    Easy_U32 spsLength;				//视频sps帧长度
    Easy_U32 ppsLength;				//视频pps帧长度
    Easy_U32 seiLength;				//视频sei帧长度
    Easy_U8	 vps[256];			//视频vps帧内容
    Easy_U8	 sps[256];			//视频sps帧内容
    Easy_U8	 pps[128];			//视频sps帧内容
    Easy_U8	 sei[128];			//视频sei帧内容
}EASY_RTSPSERVER_MEDIA_INFO_T;
typedef struct __RTSP_CHANNEL_T
{
    char		url[256];
    char		resourcename[256];
    char		username[36];
    char		password[36];
    EASY_RTSPSERVER_MEDIA_INFO_T mediaInfo;
    SIMPLE_MEDIA_INFO_T s_mediaInfo;

    Simple_Handle   rtspClientHandle;//rtsp客户端句柄
    void* channelHandle;
    int status;
}RTSP_CHANNEL_T;

// 流状态
typedef enum
{
    STREAM_IDLE,
    STREAM_CONNECTING,
    STREAM_RUNNING,
    STREAM_STOPPING,
    STREAM_ERROR
} StreamState;

// 流类型:区分 网络RTSP / USB摄像头
typedef enum
{
    SRC_TYPE_NETWORK,    // RTSP/RTMP 网络流:解封装取AVPacket
    SRC_TYPE_USB_CAMERA,  // USB摄像头:采集原始帧 + 编码为AVPacket
    SRC_TYPE_LOCAL_FILE   //本地文件
} StreamSrcType;

#define EXTRA_DATA_MAX_LEN  2048    // SPS/PPS 最大缓存长度

// 单路流上下文 (Simple_Handle 本质为此结构体指针)
typedef struct StreamContext
{
    // 基础配置
    int                 log_level;
    char                stream_url[256];
    SIMPLE_RTP_CONNECT_TYPE conn_type;
    void*               user_ptr;
    int                 auto_reconn;
    int                 timeout_ms;
    int                 use_extra_data;

    // 回调函数
    SimpleStreamClientCallBack pkt_cb;

    // 流类型 & 状态
    StreamSrcType       src_type;
    std::atomic<int>          state;
    std::atomic<int>          thread_exit;
    int                 thread_running;

    // FFmpeg 上下文
    AVFormatContext*    fmt_ctx;
    AVBSFContext*       bsf_ctx;
    int                 video_stream_idx;
    int                 audio_stream_idx;

    // ========== SPS/PPS 缓存 ==========
    uint8_t             video_extradata[EXTRA_DATA_MAX_LEN];
    int                 video_extradata_len;

    // -------- USB 摄像头专属:采集 + 编码 --------
    AVCodecContext*     enc_ctx;    // 视频编码器
    const AVCodec*      encoder;
    SwsContext *sws_ctx ;
    int                 cam_width;
    int                 cam_height;
    enum AVPixelFormat  cam_pix_fmt;

    // 线程 & 锁
    pthread_t           work_thread;
    pthread_mutex_t     ctx_mutex;

    // 帧信息结构体(透传给上层)
    SIMPLE_FRAME_INFO   frame_info;

} StreamContext;


#endif //SIMPLESTREAMCLIENT_BASETYPE_H

SimpleStreamClientAPI.cpp

cpp 复制代码
#include "SimpleStreamClientAPI.h"
#include "BaseType.h"
#include <iostream>

#define MEDIA_INFO_BUF_VPS_SPS_MAX  256
#define MEDIA_INFO_BUF_PPS_SEI_MAX  128
// 转换返回码
typedef enum
{
    CONV_RET_OK = 0,
    CONV_RET_NULL_INPUT = -1,
    CONV_RET_BUF_SHORT = -2,
    CONV_RET_ALLOC_FAIL = -3,
} ConvRetCode;
// 内部线程入口函数声明
static void* StreamWorkThread(void* arg);
static StreamSrcType DetectStreamType(const char *url);


// 根据编码获取对应BSF名称
static const char* GetMp4ToAnnexBBsfName(enum AVCodecID codecId)
{
    if (codecId == AV_CODEC_ID_H264)
        return "h264_mp4toannexb";
    if (codecId == AV_CODEC_ID_HEVC)
        return "hevc_mp4toannexb";
    return NULL;
}
/**
 * @brief 初始化AVCC/HVCC转Annex-B比特流过滤器
 * @param parIn 视频流codecpar(mp4解封装得到)
 * @param bsfCtx [out] 输出BSF上下文
 * @return 0成功 负数失败
 */
int InitMp4ToAnnexBBsf(const AVCodecParameters* parIn, AVBSFContext** bsfCtx)
{
    *bsfCtx = NULL;
    if (!parIn || parIn->codec_type != AVMEDIA_TYPE_VIDEO)
        return -1;

    const char* bsfName = GetMp4ToAnnexBBsfName(parIn->codec_id);
    if (!bsfName)
        return -2;

    // 1. 获取过滤器
    const AVBitStreamFilter* filter = av_bsf_get_by_name(bsfName);
    if (!filter)
        return -3;

    // 2. 分配BSF上下文
    int ret = av_bsf_alloc(filter, bsfCtx);
    if (ret < 0)
        return ret;

    // 3. 拷贝编码参数(关键:携带extradata SPS/PPS/VPS)
    ret = avcodec_parameters_copy((*bsfCtx)->par_in, parIn);
    if (ret < 0)
    {
        av_bsf_free(bsfCtx);
        return ret;
    }

    // 4. 初始化过滤器
    ret = av_bsf_init(*bsfCtx);
    if (ret < 0)
    {
        av_bsf_free(bsfCtx);
        return ret;
    }
    return 0;
}
/**
 * @brief 转换1帧MP4 AVCC/HVCC packet为Annex-B packet
 * @param bsfCtx 已初始化的BSF上下文
 * @param inPkt 输入mp4原始packet
 * @param outPkt 输出Annex-B标准packet(调用者av_packet_unref释放)
 * @return 0成功;AVERROR(EAGAIN)无输出包;负数失败
 */
int ConvertPacketToAnnexB(AVBSFContext* bsfCtx, const AVPacket* inPkt, AVPacket* outPkt)
{
    av_packet_unref(outPkt);

    // 送入过滤器
    int ret = av_bsf_send_packet(bsfCtx, (AVPacket*)inPkt);
    if (ret < 0)
        return ret;

    // 取出转换后的Annex-B包
    ret = av_bsf_receive_packet(bsfCtx, outPkt);
    return ret;
}

// 辅助函数:查找 NALU 起始码位置 & 返回 NALU 数据起始偏移
static int FindNalStartCode(const uint8_t* data, int totalLen, int startPos, int* nalSize)
{
    if (!data || totalLen <= 0 || startPos >= totalLen || !nalSize)
        return -1;

    *nalSize = 0;
    for (int i = startPos; i < totalLen ; ++i)
    {
        // 匹配 00 00 00 01 (4字节起始码)
        if (data[i] == 0x00 && data[i+1] == 0x00 && data[i+2] == 0x00 && data[i+3] == 0x01)
        {
            int nextStart = i + 4;
            // 查找下一个起始码,确定当前NALU长度
            int j = nextStart;
            while (j < totalLen)
            {
                if (data[j] == 0x00 && data[j+1] == 0x00 && data[j+2] == 0x00 && data[j+3] == 0x01)
                    break;
                j++;
            }
            *nalSize = j - nextStart;
            return nextStart;
        }
        // 匹配 00 00 01 (3字节起始码)
        else if (data[i] == 0x00 && data[i+1] == 0x00 && data[i+2] == 0x01)
        {
            int nextStart = i + 3;
            int j = nextStart;
            while (j < totalLen - 2)
            {
                if (data[j] == 0x00 && data[j+1] == 0x00 && data[j+2] == 0x01)
                    break;
                j++;
            }
            *nalSize = j - nextStart;
            return nextStart;
        }
    }
    return -1; // 未找到起始码
}
/**
 * @brief 解析mp4 extradata(AVCC/HVCC) 提取VPS/SPS/PPS
 * @param codecId 编码类型 AV_CODEC_ID_H264 / AV_CODEC_ID_HEVC
 * @param extraData 原始extradata二进制
 * @param extraLen extradata长度
 * @param outInfo 输出解析后的vps/sps/pps
 * @return 0成功 <0失败
 */
int ParseAvcHvccExtraData(int codecId, const uint8_t* extraData, int extraLen, SIMPLE_MEDIA_INFO_T* outInfo)
{
    if (!extraData || extraLen <= 0 || !outInfo)
        return -1;

    // 清空输出缓冲区
    memset(outInfo->u8Vps, 0, MEDIA_INFO_BUF_VPS_SPS_MAX);
    outInfo->u32VpsLength = 0;
    memset(outInfo->u8Sps, 0, MEDIA_INFO_BUF_VPS_SPS_MAX);
    outInfo->u32SpsLength = 0;
    memset(outInfo->u8Pps, 0, MEDIA_INFO_BUF_PPS_SEI_MAX);
    outInfo->u32PpsLength = 0;

    int pos = 0;
    if (codecId == AV_CODEC_ID_H264)
    {
        // ========= H264 AVCC 解析 =========
        // 最小头部长度校验
        if (extraLen < 7)
            return -2;

        pos = 4; // 跳过版本+profile+level+mask(01 4d 40 28 ff)
        uint8_t spsCntFlag = extraData[++pos];

        // SPS数量
        uint8_t spsCount = spsCntFlag & 0x1F;
        for (int i = 0; i < spsCount; i++)
        {
            if (pos + 2 > extraLen)
                return -3;
            uint16_t spsLen = (extraData[pos+1] << 8) | extraData[pos + 2];
            pos += 2;

            if (pos + spsLen > extraLen)
                return -4;

            // 拷贝SPS
            int copyLen = (spsLen < MEDIA_INFO_BUF_VPS_SPS_MAX) ? spsLen : MEDIA_INFO_BUF_VPS_SPS_MAX;
            memcpy(outInfo->u8Sps, extraData + pos, copyLen);
            outInfo->u32SpsLength = copyLen;

            pos += spsLen;
            break; // 只取第一个SPS
        }

        // PPS
        if (pos >= extraLen)
            return -5;
        uint8_t ppsCount = extraData[++pos];
        for (int i = 0; i < ppsCount; i++)
        {
            if (pos + 2 > extraLen)
                return -6;
            uint16_t ppsLen = (extraData[pos+1] << 8) | extraData[pos + 2];
            pos += 2;

            if (pos + ppsLen > extraLen)
                return -7;

            int copyLen = (ppsLen < MEDIA_INFO_BUF_PPS_SEI_MAX) ? ppsLen : MEDIA_INFO_BUF_PPS_SEI_MAX;
            memcpy(outInfo->u8Pps, extraData + pos, copyLen);
            outInfo->u32PpsLength = copyLen;

            pos += ppsLen;
            break; // 只取第一个PPS
        }
        // H264无VPS,保持0
    }
    else if (codecId == AV_CODEC_ID_HEVC)
    {
        // ========= H265 HVCC 解析(扩展兼容)=========
        if (extraLen < 23)
            return -8;
        pos = 22;

        // VPS
        uint8_t vpsCnt = extraData[pos++];
        for (int i = 0; i < vpsCnt; i++)
        {
            uint16_t vpsLen = (extraData[pos] << 8) | extraData[pos + 1];
            pos += 2;
            int copyLen = (vpsLen < MEDIA_INFO_BUF_VPS_SPS_MAX) ? vpsLen : MEDIA_INFO_BUF_VPS_SPS_MAX;
            memcpy(outInfo->u8Vps, extraData + pos, copyLen);
            outInfo->u32VpsLength = copyLen;
            pos += vpsLen;
            break;
        }

        // SPS
        uint8_t spsCnt = extraData[pos++];
        for (int i = 0; i < spsCnt; i++)
        {
            uint16_t spsLen = (extraData[pos] << 8) | extraData[pos + 1];
            pos += 2;
            int copyLen = (spsLen < MEDIA_INFO_BUF_VPS_SPS_MAX) ? spsLen : MEDIA_INFO_BUF_VPS_SPS_MAX;
            memcpy(outInfo->u8Sps, extraData + pos, copyLen);
            outInfo->u32SpsLength = copyLen;
            pos += spsLen;
            break;
        }

        // PPS
        uint8_t ppsCnt = extraData[pos++];
        for (int i = 0; i < ppsCnt; i++)
        {
            uint16_t ppsLen = (extraData[pos] << 8) | extraData[pos + 1];
            pos += 2;
            int copyLen = (ppsLen < MEDIA_INFO_BUF_PPS_SEI_MAX) ? ppsLen : MEDIA_INFO_BUF_PPS_SEI_MAX;
            memcpy(outInfo->u8Pps, extraData + pos, copyLen);
            outInfo->u32PpsLength = copyLen;
            pos += ppsLen;
            break;
        }
    }
    else
    {
        return -9; // 不支持的编码
    }

    return 0;
}
static void ExtractVideoParams(SIMPLE_MEDIA_INFO_T &s_media_info, AVStream *vs) {
    memset(s_media_info.u8Vps,  0, MEDIA_INFO_BUF_VPS_SPS_MAX);
    memset(s_media_info.u8Sps,  0, MEDIA_INFO_BUF_VPS_SPS_MAX);
    memset(s_media_info.u8Pps,  0, MEDIA_INFO_BUF_PPS_SEI_MAX);
    memset(s_media_info.u8Sei,  0, MEDIA_INFO_BUF_PPS_SEI_MAX);
    s_media_info.u32VpsLength  = 0;
    s_media_info.u32SpsLength  = 0;
    s_media_info.u32PpsLength  = 0;
    s_media_info.u32SeiLength  = 0;
    int pos = 0;
    int nalLen = 0;

    // 分支1:H.264 编码 (AV_CODEC_ID_H264)
    if (vs->codecpar->codec_id == AV_CODEC_ID_H264)
    {
        // H264 extradata: SPS NALU + PPS NALU
        pos = FindNalStartCode(vs->codecpar->extradata, vs->codecpar->extradata_size, pos, &nalLen);
        if (pos > 0 && nalLen > 0 && pos + nalLen <= vs->codecpar->extradata_size)
        {
            // 判断 SPS (NALU type = 0x67)
            if ((vs->codecpar->extradata[pos] & 0x1F) == 0x07)
            {
                int copyLen = (nalLen < MEDIA_INFO_BUF_VPS_SPS_MAX) ? nalLen : MEDIA_INFO_BUF_VPS_SPS_MAX;
                memcpy(s_media_info.u8Sps, vs->codecpar->extradata + pos, copyLen);
                s_media_info.u32SpsLength = copyLen;
                printf("sps:%d\n",copyLen);
                for (auto i= 0;i<s_media_info.u32SpsLength;i++) {
                    printf("%02x ",s_media_info.u8Sps[i]);
                }
                printf("\n");
            }
            // 查找下一个 NALU (PPS)
            pos += nalLen;
            pos = FindNalStartCode(vs->codecpar->extradata, vs->codecpar->extradata_size, pos, &nalLen);
            if (pos > 0 && nalLen > 0 && pos + nalLen <= vs->codecpar->extradata_size)
            {
                // 判断 PPS (NALU type = 0x68)
                if ((vs->codecpar->extradata[pos] & 0x1F) == 0x08)
                {
                    int copyLen = (nalLen < MEDIA_INFO_BUF_PPS_SEI_MAX) ? nalLen : MEDIA_INFO_BUF_PPS_SEI_MAX;
                    memcpy(s_media_info.u8Pps, vs->codecpar->extradata + pos, copyLen);
                    s_media_info.u32PpsLength = copyLen;
                    printf("pps:%d\n",copyLen);
                    for (auto i= 0;i<s_media_info.u32PpsLength;i++) {
                        printf("%02x ",s_media_info.u8Pps[i]);
                    }
                    printf("\n");
                }
            }
        }
    }
    // 分支2:H.265/HEVC 编码 (AV_CODEC_ID_HEVC)
    else if (vs->codecpar->codec_id == AV_CODEC_ID_HEVC) {
        // H265 extradata: VPS NALU + SPS NALU + PPS NALU
        // 1. 解析 VPS (NALU type = 0x40)
        pos = FindNalStartCode(vs->codecpar->extradata, vs->codecpar->extradata_size, pos, &nalLen);
        if (pos > 0 && nalLen > 0 && pos + nalLen <= vs->codecpar->extradata_size)
        {
            if ((vs->codecpar->extradata[pos]>>1 & 0x3F) == 0x20)
            {
                int copyLen = (nalLen < MEDIA_INFO_BUF_VPS_SPS_MAX) ? nalLen : MEDIA_INFO_BUF_VPS_SPS_MAX;
                memcpy(s_media_info.u8Vps, vs->codecpar->extradata + pos, copyLen);
                s_media_info.u32VpsLength = copyLen;
                printf("vps:%d\n",copyLen);
                for (auto i= 0;i<s_media_info.u32VpsLength;i++) {
                    printf("%02x ",s_media_info.u8Vps[i]);
                }
                printf("\n");
            }else {
                printf("no find vps\n");
            }

            // 2. 解析 SPS (NALU type = 0x42)
            pos += nalLen;
            pos = FindNalStartCode(vs->codecpar->extradata, vs->codecpar->extradata_size, pos, &nalLen);
            if (pos > 0 && nalLen > 0 && pos + nalLen <= vs->codecpar->extradata_size)
            {
                if ((vs->codecpar->extradata[pos]>>1 & 0x3F) == 0x21)
                {
                    int copyLen = (nalLen < MEDIA_INFO_BUF_VPS_SPS_MAX) ? nalLen : MEDIA_INFO_BUF_VPS_SPS_MAX;
                    memcpy(s_media_info.u8Sps, vs->codecpar->extradata + pos, copyLen);
                    s_media_info.u32SpsLength = copyLen;
                    printf("sps:%d\n",copyLen);
                    for (auto i= 0;i<s_media_info.u32SpsLength;i++) {
                        printf("%02x ",s_media_info.u8Sps[i]);
                    }
                    printf("\n");
                }

                // 3. 解析 PPS (NALU type = 0x44)
                pos += nalLen;
                pos = FindNalStartCode(vs->codecpar->extradata, vs->codecpar->extradata_size, pos, &nalLen);
                if (pos > 0 && nalLen > 0 && pos + nalLen <= vs->codecpar->extradata_size)
                {
                    if ((vs->codecpar->extradata[pos]>>1 & 0x3F) == 0x22)
                    {
                        int copyLen = (nalLen < MEDIA_INFO_BUF_PPS_SEI_MAX) ? nalLen : MEDIA_INFO_BUF_PPS_SEI_MAX;
                        memcpy(s_media_info.u8Pps, vs->codecpar->extradata + pos, copyLen);
                        s_media_info.u32PpsLength = copyLen;
                        printf("pps:%d\n",copyLen);
                        for (auto i= 0;i<s_media_info.u32PpsLength;i++) {
                            printf("%02x ",s_media_info.u8Pps[i]);
                        }
                        printf("\n");
                    }
                }
            }
        }
    }
}
int SimpleStreamClient_Init(void **handle, int loglevel)
{
    if (!handle)
        return -1;

    StreamContext* ctx = (StreamContext*)av_mallocz(sizeof(StreamContext));
    if (!ctx)
        return -2;

    // 基础初始化
    ctx->log_level = loglevel;
    ctx->pkt_cb = NULL;
    ctx->user_ptr = NULL;
    ctx->auto_reconn = 0;
    ctx->timeout_ms = 0;
    ctx->use_extra_data = 0;

    ctx->state.store(STREAM_IDLE);
    ctx->thread_exit.store(0);
    ctx->thread_running = 0;

    ctx->fmt_ctx = NULL;
    ctx->video_stream_idx = -1;
    ctx->audio_stream_idx = -1;

    // 清空 SPS/PPS 缓存
    memset(ctx->video_extradata, 0, EXTRA_DATA_MAX_LEN);
    ctx->video_extradata_len = 0;

    // USB 编码相关置空
    ctx->enc_ctx = NULL;
    ctx->encoder = NULL;
    ctx->cam_width  = 640;
    ctx->cam_height = 480;
    ctx->cam_pix_fmt = AV_PIX_FMT_YUV420P;

    // 初始化锁
    if (pthread_mutex_init(&ctx->ctx_mutex, NULL) != 0)
    {
        av_free(ctx);
        return -3;
    }

    // FFmpeg 全局初始化(含设备)
    avdevice_register_all();
    av_log_set_level(loglevel ? AV_LOG_DEBUG : AV_LOG_QUIET);

    *handle = (Simple_Handle)ctx;
    return 0;
}

int SimpleStreamClient_Deinit(void *handle) {
    if (!handle)
        return -1;

    StreamContext* ctx = (StreamContext*)handle;

    // 通知线程退出
    ctx->thread_exit.store(1);
    ctx->state.store(STREAM_STOPPING);

    if (ctx->thread_running)
    {
        pthread_join(ctx->work_thread, NULL);
        ctx->thread_running = 0;
    }

    // 释放所有 FFmpeg 资源
    pthread_mutex_lock(&ctx->ctx_mutex);
    if (ctx->fmt_ctx)
    {
        avformat_close_input(&ctx->fmt_ctx);
        ctx->fmt_ctx = NULL;
    }
    avcodec_free_context(&ctx->enc_ctx);
    pthread_mutex_unlock(&ctx->ctx_mutex);

    pthread_mutex_destroy(&ctx->ctx_mutex);
    av_free(ctx);
    return 0;
}

int SimpleStreamClient_SetCallback(void *handle, SimpleStreamClientCallBack callback) {
    if (!handle || !callback)
        return -1;

    StreamContext* ctx = (StreamContext*)handle;
    pthread_mutex_lock(&ctx->ctx_mutex);
    ctx->pkt_cb = callback;
    pthread_mutex_unlock(&ctx->ctx_mutex);
    return 0;
}

int SimpleStreamClient_OpenStream(void *handle, char *url, SIMPLE_RTP_CONNECT_TYPE connType, void *userPtr, int reconn,
    int timeout, int useExtraData) {
    if (!handle || !url)
        return -1;

    StreamContext* ctx = (StreamContext*)handle;
    pthread_mutex_lock(&ctx->ctx_mutex);

    int state = ctx->state;
    if (state != STREAM_IDLE && state != STREAM_ERROR)
    {
        pthread_mutex_unlock(&ctx->ctx_mutex);
        return -2;
    }
    std::string stream_url = "";

    if (strstr(url, "ch1")) {
        stream_url = "rtsp://admin:admin12345@192.168.22.222:554/Streaming/Channels/101?transportmode=unicast&profile=Profile_1";
    }else if (strstr(url, "ch2")) {
        stream_url = "/home/packet/car1.mp4";
    }else if (strstr(url,"ch3")) {
        stream_url = "/home/packet/easy.mp4";
    }else if (strstr(url,"ch4")) {
        stream_url = "rtsp://admin5:admin12345@192.168.250.114:554/cam/realmonitor?channel=1&subtype=0&unicast=true&proto=Onvif";
    }else if (strstr(url,"ch5")) {
        stream_url = "/home/packet/dzq_h265.mp4";
    }else if (strstr(url,"camera")) {
        stream_url = "camera";
    }else {
        stream_url = url;
    }
    // 赋值参数
    if (stream_url == "") {

    }else {
        strncpy(ctx->stream_url, stream_url.data(), sizeof(ctx->stream_url) - 1);
    }
    ctx->conn_type = connType;
    ctx->user_ptr = userPtr;
    ctx->auto_reconn = reconn;
    ctx->timeout_ms = timeout;
    ctx->use_extra_data = useExtraData;

    // 判断流类型
    ctx->src_type = DetectStreamType(stream_url.data());

    // 清空旧的 SPS/PPS
    memset(ctx->video_extradata, 0, EXTRA_DATA_MAX_LEN);
    ctx->video_extradata_len = 0;
    ctx->state.store(STREAM_CONNECTING);
    pthread_mutex_unlock(&ctx->ctx_mutex);

    // 启动工作线程
    if (pthread_create(&ctx->work_thread, NULL, StreamWorkThread, ctx) != 0)
    {
        ctx->state.store(STREAM_ERROR);
        return -3;
    }
    ctx->thread_running = 1;
    return 0;
}
// 内部:判断是网络流还是USB摄像头设备
static StreamSrcType DetectStreamType(const char *url)
{
    if (!url) return SRC_TYPE_NETWORK;
    // 简单规则:rtsp/rtmp/http 开头 = 网络流
    if (strstr(url, "rtsp://") || strstr(url, "rtmp://") || strstr(url, "http://"))
    {
        return SRC_TYPE_NETWORK;
    }else if (strstr(url,"camera")) {
        return SRC_TYPE_USB_CAMERA;
    }
    // 其余当作 本地文件
    return SRC_TYPE_LOCAL_FILE;
}
// 内部:缓存 SPS/PPS
static void CacheExtraData(StreamContext* ctx, const uint8_t* data, int len)
{
    if (!ctx || !data || len <= 0) return;
    int copyLen = (len < EXTRA_DATA_MAX_LEN) ? len : EXTRA_DATA_MAX_LEN;
    memcpy(ctx->video_extradata, data, copyLen);
    ctx->video_extradata_len = copyLen;
    printf("video_extradata_len:%d\n",ctx->video_extradata_len);
}


// 工作线程主逻辑
static void* StreamWorkThread(void* arg)
{
    StreamContext* ctx = (StreamContext*)arg;
    if (!ctx) return NULL;

    RTSP_CHANNEL_T *rtsp_channel = (RTSP_CHANNEL_T*)ctx->user_ptr;

    AVPacket* pkt = av_packet_alloc();
    AVPacket* outPkt = av_packet_alloc();
    AVFrame*  frame = av_frame_alloc();
    // 固定帧参数:宽高、像素格式(和采集、编码器严格对齐)
    frame->width  = ctx->cam_width;
    frame->height = ctx->cam_height;
    frame->format = ctx->cam_pix_fmt;


    while (!ctx->thread_exit.load())
    {
        pthread_mutex_lock(&ctx->ctx_mutex);
        ctx->fmt_ctx = avformat_alloc_context();
        AVDictionary* opts = NULL;
        av_dict_set(&opts, "protocol_whitelist", "tcp,udp,rtsp,file,rtp,http,https", 0);
#if 0
        av_dict_set(&opts, "timeout", std::to_string(ctx->timeout_ms * 1000).data(), 0);
#else
        if (ctx->timeout_ms > 0)
        {
            char stimeout_str[32] = {0};
            snprintf(stimeout_str, sizeof(stimeout_str), "%d", ctx->timeout_ms * 1000);
            av_dict_set(&opts, "stimeout", stimeout_str, 0);
        }
#endif
        av_dict_set(&opts, "rtsp_transport", "tcp", 0);
        int ret = -1;
        // ========== 分支1:RTSP/网络流 解封装读包 ==========
        if (ctx->src_type == SRC_TYPE_NETWORK )
        {
            ret = avformat_open_input(&ctx->fmt_ctx, ctx->stream_url, NULL, &opts);
        } else if (ctx->src_type == SRC_TYPE_LOCAL_FILE) {
            ret = avformat_open_input(&ctx->fmt_ctx, ctx->stream_url, NULL, &opts);
        }
        // ========== 分支2:USB 摄像头 设备采集 ==========
        else
        {

            // 预先分配AVFrame内部缓冲区
            int retValue = av_frame_get_buffer(frame, 0);
            if (retValue < 0)
            {
                av_log(NULL, AV_LOG_ERROR, "av_frame_get_buffer failed\n");
                continue;
            }else {
                av_log(NULL, AV_LOG_INFO, "av_frame_get_buffer succ\n");
            }

            // 指定设备输入格式(linux:video4linux2, windows:dshow)
            AVDictionary* dev_opts = NULL;

            AVInputFormat *ifmt = av_find_input_format("video4linux2");
            // Linux USB摄像头默认设备:/dev/video0
            const char *videoDevice = "/dev/video0";
            ret = avformat_open_input(&ctx->fmt_ctx, videoDevice, ifmt, &dev_opts);

            // 初始化编码器 (H.264 示例)
            if (ret >= 0 && !ctx->enc_ctx)
            {
                ctx->encoder = avcodec_find_encoder(AV_CODEC_ID_H264);
                if (ctx->encoder == nullptr) {
                    printf("avcodec_find_encoder failed\n");
                }else {
                    printf("avcodec_find_encoder() call succ \n");
                }
                ctx->enc_ctx = avcodec_alloc_context3(ctx->encoder);
                ctx->enc_ctx->width  = ctx->cam_width;
                ctx->enc_ctx->height = ctx->cam_height;
                ctx->enc_ctx->pix_fmt = ctx->cam_pix_fmt;
                ctx->enc_ctx->time_base = (AVRational){1, 25};

                ctx->enc_ctx->gop_size = 25; // 每25帧一个I帧
                // 禁止生成B帧,只输出 I帧 + P帧
                ctx->enc_ctx->max_b_frames = 0;
                // libx264 推荐CRF 23~28,数值越小越清晰
                av_opt_set(ctx->enc_ctx->priv_data, "crf", "26", 0);

                int res = avcodec_open2(ctx->enc_ctx, ctx->encoder, NULL);
                if (res < 0) {
                    printf("avcodec_open2() call failed\n");
                }else {
                    printf("avcodec_open2() call succ\n");
                    printf("extradata_size:%d\n",ctx->enc_ctx->extradata_size);
                }
                // 1. 创建输出流
                AVStream* vstream = avformat_new_stream(ctx->fmt_ctx, ctx->enc_ctx->codec);
                if (!vstream)
                {
                    // 创建流失败
                    printf("avformat_new_stream() call failed\n");
                    continue;
                }else {
                    printf("avformat_new_stream() call succ\n");
                }

                // 2. 把编码器上下文同步到流参数(fmt_ctx依靠此参数封装)
                int copy_ret = avcodec_parameters_from_context(vstream->codecpar, ctx->enc_ctx);
                if (copy_ret < 0)
                {
                    av_log(NULL, AV_LOG_ERROR, "copy codec param failed\n");
                    continue;
                }else {
                    printf("avcodec_parameters_from_context() call succ ,codeid:%d\n",
                        vstream->codecpar->codec_id);
                    printf("ctx->fmt_ctx->nb_streams:%d \n",ctx->fmt_ctx->nb_streams);

                }

                // 3. 同步时基,封装pts/dts计算依赖
                vstream->time_base = ctx->enc_ctx->time_base;
                // 初始化SwsContext
                ctx->sws_ctx = sws_getContext(
                    ctx->cam_width, ctx->cam_height, AV_PIX_FMT_YUYV422,
                    ctx->enc_ctx->width, ctx->enc_ctx->height, AV_PIX_FMT_YUV420P,
                    SWS_BILINEAR, NULL, NULL, NULL
                );
                //测试代码
                {
                    for (int i = 0;i<ctx->fmt_ctx->nb_streams;i++) {
                        AVStream* vs = ctx->fmt_ctx->streams[i];
                        printf("-=- codeid:%d\n",vs->codecpar->codec_id);
                    }
                }
            }
        }
        av_dict_free(&opts);

        if (ret < 0)
        {
            pthread_mutex_unlock(&ctx->ctx_mutex);
            ctx->state.store(STREAM_ERROR);
            av_usleep(500000);
            if (!ctx->auto_reconn) break;
            continue;
        }

        // 查找流信息
        ret = avformat_find_stream_info(ctx->fmt_ctx, NULL);
        if (ret < 0)
        {
            avformat_close_input(&ctx->fmt_ctx);
            ctx->fmt_ctx = NULL;
            pthread_mutex_unlock(&ctx->ctx_mutex);
            ctx->state.store(STREAM_ERROR);
            av_usleep(500000);
            if (!ctx->auto_reconn) break;
            continue;
        }

        // 找到音视频流
        if (ctx->src_type == SRC_TYPE_USB_CAMERA) {
            ctx->video_stream_idx = av_find_best_stream(ctx->fmt_ctx, AVMEDIA_TYPE_VIDEO, 1, -1, NULL, 0);
        }else {
            ctx->video_stream_idx = av_find_best_stream(ctx->fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
        }
        ctx->audio_stream_idx = av_find_best_stream(ctx->fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
        printf("video_stream_idx:%d audio_stream_idx:%d\n",ctx->video_stream_idx,ctx->audio_stream_idx);
        // 缓存首次 SPS/PPS
        if (ctx->video_stream_idx >= 0)
        {
            AVStream* vs = ctx->fmt_ctx->streams[ctx->video_stream_idx];
            if (vs->codecpar->codec_id == AV_CODEC_ID_H264) {
                rtsp_channel->s_mediaInfo.u32VideoCodec = 0x1C;
            }else if (vs->codecpar->codec_id == AV_CODEC_ID_HEVC) {
                rtsp_channel->s_mediaInfo.u32VideoCodec = 0xAE;
            }else {
                printf("==codec id :%d\n",vs->codecpar->codec_id);
            }
            rtsp_channel->s_mediaInfo.u32VideoFps = 25;
            if (vs->codecpar->extradata && vs->codecpar->extradata_size > 0)
            {
                if (ctx->src_type == SRC_TYPE_NETWORK || ctx->src_type == SRC_TYPE_USB_CAMERA)
                {
                    ExtractVideoParams(rtsp_channel->s_mediaInfo,vs);
                }
                else if (ctx->src_type == SRC_TYPE_LOCAL_FILE)
                {
#if 0
                    int result = ParseAvcHvccExtraData(vs->codecpar->codec_id,
                        vs->codecpar->extradata, vs->codecpar->extradata_size,
                        &rtsp_channel->s_mediaInfo);
                    if (result == 0) {
                        printf("ParseAvcHvccExtraData() parse successful\n");
                    }else {
                        printf("ParseAvcHvccExtraData parse failed :%d\n",result);
                    }
#else
                    // 初始化转换过滤器
                    ret = InitMp4ToAnnexBBsf(vs->codecpar, &ctx->bsf_ctx);
                    if (ret < 0) {
                        printf("InitMp4ToAnnexBBsf() failed\n");
                        pthread_mutex_unlock(&ctx->ctx_mutex);
                        continue;
                    }
#endif

                }
                rtsp_channel->mediaInfo.videoCodec = rtsp_channel->s_mediaInfo.u32VideoCodec;
                rtsp_channel->mediaInfo.videoFps = rtsp_channel->s_mediaInfo.u32VideoFps;
                if (rtsp_channel->s_mediaInfo.u32SpsLength > 0) {
                    rtsp_channel->mediaInfo.spsLength = rtsp_channel->s_mediaInfo.u32SpsLength;
                    memcpy(rtsp_channel->mediaInfo.sps,rtsp_channel->s_mediaInfo.u8Sps,rtsp_channel->s_mediaInfo.u32SpsLength);
                }
                if (rtsp_channel->s_mediaInfo.u32PpsLength > 0) {
                    rtsp_channel->mediaInfo.ppsLength = rtsp_channel->s_mediaInfo.u32PpsLength;
                    memcpy(rtsp_channel->mediaInfo.pps,rtsp_channel->s_mediaInfo.u8Pps,rtsp_channel->s_mediaInfo.u32PpsLength);
                }
                if (rtsp_channel->s_mediaInfo.u32VpsLength > 0) {
                    rtsp_channel->mediaInfo.vpsLength = rtsp_channel->s_mediaInfo.u32VpsLength;
                    memcpy(rtsp_channel->mediaInfo.vps,rtsp_channel->s_mediaInfo.u8Vps,rtsp_channel->s_mediaInfo.u32VpsLength);
                }
                rtsp_channel->mediaInfo.seiLength = 0;
                if (rtsp_channel->s_mediaInfo.u32SeiLength > 0) {
                    rtsp_channel->mediaInfo.seiLength = rtsp_channel->s_mediaInfo.u32SeiLength;
                    memcpy(rtsp_channel->mediaInfo.sei,rtsp_channel->s_mediaInfo.u8Sei,rtsp_channel->s_mediaInfo.u32SeiLength);
                }
                printf("extradata_size:%d\n",vs->codecpar->extradata_size);
                for (int i = 0; i < vs->codecpar->extradata_size; i++) {
                    printf(" %02x", vs->codecpar->extradata[i]);
                }
                printf("\n");
                CacheExtraData(ctx, vs->codecpar->extradata, vs->codecpar->extradata_size);
            }else {
                printf("extradata_size:%d\n",vs->codecpar->extradata_size);
            }
        }
        if (ctx->audio_stream_idx >= 0) {
            AVStream* as = ctx->fmt_ctx->streams[ctx->audio_stream_idx];
            rtsp_channel->s_mediaInfo.u32AudioCodec = as->codecpar->codec_id;
            rtsp_channel->s_mediaInfo.u32AudioChannel = as->codecpar->channels;
            rtsp_channel->s_mediaInfo.u32AudioSamplerate = as->codecpar->sample_rate;
            rtsp_channel->s_mediaInfo.u32AudioBitsPerSample = as->codecpar->bits_per_coded_sample;

            rtsp_channel->mediaInfo.audioSampleRate = rtsp_channel->s_mediaInfo.u32AudioSamplerate;
            rtsp_channel->mediaInfo.audioBitsPerSample = rtsp_channel->s_mediaInfo.u32AudioBitsPerSample;
            rtsp_channel->mediaInfo.audioCodec = rtsp_channel->s_mediaInfo.u32AudioCodec;
            rtsp_channel->mediaInfo.audioChannel = rtsp_channel->s_mediaInfo.u32AudioChannel;
        }
        ctx->state.store(STREAM_RUNNING);
        pthread_mutex_unlock(&ctx->ctx_mutex);
        // ========== 循环读包 ==========
        if (ctx->src_type == SRC_TYPE_NETWORK)
        {
            // 网络流:直接读出 AVPacket 回调
            while (!ctx->thread_exit.load() && av_read_frame(ctx->fmt_ctx, pkt) >= 0)
            {
                memset(&ctx->frame_info, 0, sizeof(SIMPLE_FRAME_INFO));
                ctx->frame_info.pts = pkt->pts;
                ctx->frame_info.dts = pkt->dts;
                ctx->frame_info.length = pkt->size;
                if (pkt->flags & AV_PKT_FLAG_KEY) {
                    ctx->frame_info.type = 0x01;
                }

                if (pkt->stream_index == ctx->video_stream_idx)
                {
                    ctx->frame_info.codec = rtsp_channel->s_mediaInfo.u32VideoCodec;

                    if (ctx->pkt_cb)
                    {
                       ctx->pkt_cb(ctx->user_ptr, 1, pkt->data, &ctx->frame_info);
                    }
                }
                else if (pkt->stream_index == ctx->audio_stream_idx)
                {
                    if (ctx->pkt_cb)
                    {
                       ctx->pkt_cb(ctx->user_ptr, 2, pkt->data,  &ctx->frame_info);
                    }
                }
                av_packet_unref(pkt);
            }
        }
        else if (ctx->src_type == SRC_TYPE_LOCAL_FILE) {
            // 文件流:直接读出 AVPacket 回调
            while (!ctx->thread_exit.load() && av_read_frame(ctx->fmt_ctx, pkt) >= 0)
            {
                memset(&ctx->frame_info, 0, sizeof(SIMPLE_FRAME_INFO));
                if (pkt->stream_index == ctx->video_stream_idx)
                {
                    ctx->frame_info.codec = rtsp_channel->s_mediaInfo.u32VideoCodec;
                    AVStream *av = ctx->fmt_ctx->streams[ctx->video_stream_idx];
                    ret = ConvertPacketToAnnexB(ctx->bsf_ctx, pkt, outPkt);
                    if (ret == 0) {
                        ctx->frame_info.pts = outPkt->pts;
                        ctx->frame_info.dts = outPkt->dts;
                        ctx->frame_info.length = outPkt->size;
                        if (outPkt->flags & AV_PKT_FLAG_KEY) {
                            ctx->frame_info.type = 0x01;
                        }
                        if (ctx->pkt_cb)
                        {
                            ctx->pkt_cb(ctx->user_ptr, 1, outPkt->data, &ctx->frame_info);
                        }
                    }else if (ret == AVERROR(EAGAIN))
                    {
                        // 当前包无输出,等待下一帧
                    }
                    av_usleep(rtsp_channel->s_mediaInfo.u32VideoFps*1000);
                }
                else if (pkt->stream_index == ctx->audio_stream_idx)
                {
                    if (ctx->pkt_cb)
                    {
                        //ctx->pkt_cb(ctx->user_ptr, 2, pkt->data,  &ctx->frame_info);
                    }
                }
                av_packet_unref(outPkt);
                av_packet_unref(pkt);
            }
        }
        else
        {
            // 线程初始化定义计数器
            int64_t pts_counter = 0;
            // USB摄像头:采集原始帧 -> 编码为 AVPacket -> 回调
            //1.读取USB摄像头原始未压缩YUV AVPacket
            while (!ctx->thread_exit.load() && av_read_frame(ctx->fmt_ctx, pkt) >= 0)
            {

                // 2. 将采集到的Packet数据填充到AVFrame
                // 保证frame可写入
#if 1
                if (av_frame_is_writable(frame) < 0)
                {
                    av_log(nullptr,AV_LOG_ERROR,"frame is not writable\n");
                    if (int errnum = av_frame_make_writable(frame) < 0)
                    {
                        char buf[AV_ERROR_MAX_STRING_SIZE] = {0};
                        av_make_error_string(buf, AV_ERROR_MAX_STRING_SIZE, errnum);
                        av_log(NULL, AV_LOG_ERROR, "av_frame_make_writable() call  failed "
                                                   "error info:%s\n",buf);
                        av_packet_unref(pkt);
                        continue;
                    }
                }
                else
                {
                    // av_log(nullptr,AV_LOG_INFO,"frame width:%d height:%d\n",frame->width,frame->height);
                }
#endif
#if 0
                uint8_t* src = pkt->data;
                int src_off = 0;
                // Y平面
                for (int y = 0; y < frame->height; y++)
                {
                    memcpy(frame->data[0] + y * frame->linesize[0], src + src_off, frame->width);
                    src_off += frame->width;
                }
                // U平面
                int uv_h = frame->height / 2;
                int uv_w = frame->width / 2;
                for (int y = 0; y < uv_h; y++)
                {
                    memcpy(frame->data[1] + y * frame->linesize[1], src + src_off, uv_w);
                    src_off += uv_w;
                }
                // V平面
                for (int y = 0; y < uv_h; y++)
                {
                    memcpy(frame->data[2] + y * frame->linesize[2], src + src_off, uv_w);
                    src_off += uv_w;
                }
#else
                uint8_t *src_data[1] = {pkt->data};
                int src_linesize[1] = {pkt->size / ctx->cam_height};
                sws_scale(ctx->sws_ctx, src_data, src_linesize, 0, ctx->cam_height, frame->data, frame->linesize);

# endif
                // 强制连续递增PTS,消除时序警告
                frame->pts = pts_counter++;
                // 3. 送入编码器
                int send_ret = avcodec_send_frame(ctx->enc_ctx, frame);
                if (send_ret < 0)
                {
                    av_log(NULL, AV_LOG_ERROR, "send frame to encoder fail ret:%d\n", send_ret);
                    // av_log(nullptr,AV_LOG_ERROR, "frame width:%d height:%d\n", frame->width,frame->height);
                    av_packet_unref(pkt);
                    continue;
                }else {
                    // av_log(nullptr,AV_LOG_INFO,"send frame to encoder success\n");
                }
                // 4. 循环取出编码后的H264/H265 Annex-B AVPacket
                while (1)
                {
                    av_packet_unref(outPkt);
                    int recv_ret = avcodec_receive_packet(ctx->enc_ctx, outPkt);
                    if (recv_ret == AVERROR(EAGAIN) || recv_ret == AVERROR_EOF)
                    {
                        break; // 当前无编码输出,等待下一帧
                    }
                    if (recv_ret < 0)
                    {
                        av_log(NULL, AV_LOG_ERROR, "recv encode pkt fail ret:%d\n", recv_ret);
                        break;
                    }

                    // 填充帧信息
                    memset(&ctx->frame_info, 0, sizeof(SIMPLE_FRAME_INFO));
                    ctx->frame_info.pts = outPkt->pts;
                    ctx->frame_info.dts = outPkt->dts;
                    ctx->frame_info.length = outPkt->size;
                    ctx->frame_info.type = (outPkt->flags & AV_PKT_FLAG_KEY) ? 1 : 0;

                    ctx->frame_info.codec = rtsp_channel->s_mediaInfo.u32VideoCodec;

                    // 触发回调:补齐全部参数 (channelPtr, frameType, pBuf, dataLen, info)
                    if (ctx->pkt_cb)
                    {
                        ctx->pkt_cb(ctx->user_ptr, 1, outPkt->data,  &ctx->frame_info);
                    }
                }
                av_packet_unref(pkt);
            }
        }

        // 流断开,释放资源准备重连
        pthread_mutex_lock(&ctx->ctx_mutex);
        if (ctx->fmt_ctx)
        {
            avformat_close_input(&ctx->fmt_ctx);
            ctx->fmt_ctx = NULL;
        }
        pthread_mutex_unlock(&ctx->ctx_mutex);
        ctx->state.store(STREAM_ERROR);
        if (!ctx->auto_reconn) break;
        av_usleep(1000000);
    }
    printf("quit workThread\n");
    av_packet_free(&pkt);
    av_packet_free(&outPkt);
    av_frame_free(&frame);
    ctx->state.store(STREAM_IDLE);
    return NULL;
}



int SimpleStreamClient_GetVideoExtraData(void *handle, uint8_t *outData, int maxLen) {
    if (!handle || !outData || maxLen <= 0)
        return -1;

    StreamContext* ctx = (StreamContext*)handle;
    pthread_mutex_lock(&ctx->ctx_mutex);

    int copyLen = (ctx->video_extradata_len < maxLen) ? ctx->video_extradata_len : maxLen;
    memcpy(outData, ctx->video_extradata, copyLen);

    pthread_mutex_unlock(&ctx->ctx_mutex);
    return copyLen;
}

四 已经验证功能

1.可以正常获取rtsp网络流数据

代码片断如下:

复制代码
// 网络流:直接读出 AVPacket 回调
while (!ctx->thread_exit.load() && av_read_frame(ctx->fmt_ctx, pkt) >= 0)
{
    memset(&ctx->frame_info, 0, sizeof(SIMPLE_FRAME_INFO));
    ctx->frame_info.pts = pkt->pts;
    ctx->frame_info.dts = pkt->dts;
    ctx->frame_info.length = pkt->size;
    if (pkt->flags & AV_PKT_FLAG_KEY) {
        ctx->frame_info.type = 0x01;
    }

    if (pkt->stream_index == ctx->video_stream_idx)
    {
        ctx->frame_info.codec = rtsp_channel->s_mediaInfo.u32VideoCodec;

        if (ctx->pkt_cb)
        {
           ctx->pkt_cb(ctx->user_ptr, 1, pkt->data, &ctx->frame_info);
        }
    }
    else if (pkt->stream_index == ctx->audio_stream_idx)
    {
        if (ctx->pkt_cb)
        {
           ctx->pkt_cb(ctx->user_ptr, 2, pkt->data,  &ctx->frame_info);
        }
    }
    av_packet_unref(pkt);
}

2.可以正常获取mp4本地文件媒体流数据

代码片断如下:

复制代码
// 文件流:直接读出 AVPacket 回调
while (!ctx->thread_exit.load() && av_read_frame(ctx->fmt_ctx, pkt) >= 0)
{
    memset(&ctx->frame_info, 0, sizeof(SIMPLE_FRAME_INFO));
    if (pkt->stream_index == ctx->video_stream_idx)
    {
        ctx->frame_info.codec = rtsp_channel->s_mediaInfo.u32VideoCodec;
        AVStream *av = ctx->fmt_ctx->streams[ctx->video_stream_idx];
        ret = ConvertPacketToAnnexB(ctx->bsf_ctx, pkt, outPkt);
        if (ret == 0) {
            ctx->frame_info.pts = outPkt->pts;
            ctx->frame_info.dts = outPkt->dts;
            ctx->frame_info.length = outPkt->size;
            if (outPkt->flags & AV_PKT_FLAG_KEY) {
                ctx->frame_info.type = 0x01;
            }
            if (ctx->pkt_cb)
            {
                ctx->pkt_cb(ctx->user_ptr, 1, outPkt->data, &ctx->frame_info);
            }
        }else if (ret == AVERROR(EAGAIN))
        {
            // 当前包无输出,等待下一帧
        }
        av_usleep(rtsp_channel->s_mediaInfo.u32VideoFps*1000);
    }
    else if (pkt->stream_index == ctx->audio_stream_idx)
    {
        if (ctx->pkt_cb)
        {
            //ctx->pkt_cb(ctx->user_ptr, 2, pkt->data,  &ctx->frame_info);
        }
    }
    av_packet_unref(outPkt);
    av_packet_unref(pkt);
}

3.支持将mp4文件的AVCC格式数据包,转为Annex-B数据包

代码片断如下:

复制代码
/**
 * @brief 转换1帧MP4 AVCC/HVCC packet为Annex-B packet
 * @param bsfCtx 已初始化的BSF上下文
 * @param inPkt 输入mp4原始packet
 * @param outPkt 输出Annex-B标准packet(调用者av_packet_unref释放)
 * @return 0成功;AVERROR(EAGAIN)无输出包;负数失败
 */
int ConvertPacketToAnnexB(AVBSFContext* bsfCtx, const AVPacket* inPkt, AVPacket* outPkt)
{
    av_packet_unref(outPkt);

    // 送入过滤器
    int ret = av_bsf_send_packet(bsfCtx, (AVPacket*)inPkt);
    if (ret < 0)
        return ret;

    // 取出转换后的Annex-B包
    ret = av_bsf_receive_packet(bsfCtx, outPkt);
    return ret;
}

4.支持获取USB 相机采集的视频流数据

代码片断如下:

复制代码
// 线程初始化定义计数器
            int64_t pts_counter = 0;
            // USB摄像头:采集原始帧 -> 编码为 AVPacket -> 回调
            //1.读取USB摄像头原始未压缩YUV AVPacket
            while (!ctx->thread_exit.load() && av_read_frame(ctx->fmt_ctx, pkt) >= 0)
            {

                // 2. 将采集到的Packet数据填充到AVFrame
                // 保证frame可写入
#if 1
                if (av_frame_is_writable(frame) < 0)
                {
                    av_log(nullptr,AV_LOG_ERROR,"frame is not writable\n");
                    if (int errnum = av_frame_make_writable(frame) < 0)
                    {
                        char buf[AV_ERROR_MAX_STRING_SIZE] = {0};
                        av_make_error_string(buf, AV_ERROR_MAX_STRING_SIZE, errnum);
                        av_log(NULL, AV_LOG_ERROR, "av_frame_make_writable() call  failed "
                                                   "error info:%s\n",buf);
                        av_packet_unref(pkt);
                        continue;
                    }
                }
                else
                {
                    // av_log(nullptr,AV_LOG_INFO,"frame width:%d height:%d\n",frame->width,frame->height);
                }
#endif
#if 0
                uint8_t* src = pkt->data;
                int src_off = 0;
                // Y平面
                for (int y = 0; y < frame->height; y++)
                {
                    memcpy(frame->data[0] + y * frame->linesize[0], src + src_off, frame->width);
                    src_off += frame->width;
                }
                // U平面
                int uv_h = frame->height / 2;
                int uv_w = frame->width / 2;
                for (int y = 0; y < uv_h; y++)
                {
                    memcpy(frame->data[1] + y * frame->linesize[1], src + src_off, uv_w);
                    src_off += uv_w;
                }
                // V平面
                for (int y = 0; y < uv_h; y++)
                {
                    memcpy(frame->data[2] + y * frame->linesize[2], src + src_off, uv_w);
                    src_off += uv_w;
                }
#else
                uint8_t *src_data[1] = {pkt->data};
                int src_linesize[1] = {pkt->size / ctx->cam_height};
                sws_scale(ctx->sws_ctx, src_data, src_linesize, 0, ctx->cam_height, frame->data, frame->linesize);

# endif
                // 强制连续递增PTS,消除时序警告
                frame->pts = pts_counter++;
                // 3. 送入编码器
                int send_ret = avcodec_send_frame(ctx->enc_ctx, frame);
                if (send_ret < 0)
                {
                    av_log(NULL, AV_LOG_ERROR, "send frame to encoder fail ret:%d\n", send_ret);
                    // av_log(nullptr,AV_LOG_ERROR, "frame width:%d height:%d\n", frame->width,frame->height);
                    av_packet_unref(pkt);
                    continue;
                }else {
                    // av_log(nullptr,AV_LOG_INFO,"send frame to encoder success\n");
                }
                // 4. 循环取出编码后的H264/H265 Annex-B AVPacket
                while (1)
                {
                    av_packet_unref(outPkt);
                    int recv_ret = avcodec_receive_packet(ctx->enc_ctx, outPkt);
                    if (recv_ret == AVERROR(EAGAIN) || recv_ret == AVERROR_EOF)
                    {
                        break; // 当前无编码输出,等待下一帧
                    }
                    if (recv_ret < 0)
                    {
                        av_log(NULL, AV_LOG_ERROR, "recv encode pkt fail ret:%d\n", recv_ret);
                        break;
                    }

                    // 填充帧信息
                    memset(&ctx->frame_info, 0, sizeof(SIMPLE_FRAME_INFO));
                    ctx->frame_info.pts = outPkt->pts;
                    ctx->frame_info.dts = outPkt->dts;
                    ctx->frame_info.length = outPkt->size;
                    ctx->frame_info.type = (outPkt->flags & AV_PKT_FLAG_KEY) ? 1 : 0;

                    ctx->frame_info.codec = rtsp_channel->s_mediaInfo.u32VideoCodec;

                    // 触发回调:补齐全部参数 (channelPtr, frameType, pBuf, dataLen, info)
                    if (ctx->pkt_cb)
                    {
                        ctx->pkt_cb(ctx->user_ptr, 1, outPkt->data,  &ctx->frame_info);
                    }
                }
                av_packet_unref(pkt);
            }