news 2026/6/15 17:28:42

使用c/c++实现一个rtmp客户端程序

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
使用c/c++实现一个rtmp客户端程序

一 概述 

        该文章主要实现了rtmp拉流的功能。rtmp协议中的负载视频为h264格式,音频为aac格式.将接收到的流提取出h264裸码流和aac裸码流可以进行解码播放,存储和传输。该客户端程序只实现了将h264视频数据和aac音频数据存入文件.

二 程序的依赖库

     1.ssl(加密认证库)

     2.zip(压缩库)

     3.librtmp(开源库)

     4.rtmp_pull(基于librtmp库封装的拉流库)

        rtmp_pull库,参考下面链接:

https://blog.csdn.net/kk821521286/article/details/157728972?spm=1001.2014.3001.5502https://blog.csdn.net/kk821521286/article/details/157728972?spm=1001.2014.3001.5502

三 关键代码实现

    1.rtmpparse.h  

#ifndef RTMPPARSE_H #define RTMPPARSE_H #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include "rtmp_pull.h" // 宏定义:RTMP包类型(协议规定) #define RTMP_PACKET_TYPE_AUDIO 0x08 // 音频包 #define RTMP_PACKET_TYPE_VIDEO 0x09 // 视频包 #define RTMP_PACKET_TYPE_AMF0_DATA 0x12 // AMF0数据(包含onMetaData元数据) // 视频编码ID(RTMP协议) #define RTMP_VIDEO_CODEC_H264 0x07 // H.264/AVC // 音频编码ID(RTMP协议) #define RTMP_AUDIO_CODEC_AAC 0x0A // AAC // 帧类型(视频包高4位) #define RTMP_VIDEO_FRAME_KEY 0x10 // 关键帧(I帧) #define RTMP_VIDEO_FRAME_SEQ_HEADER 0x00 // AVC序列头(SPS/PPS) // AAC包类型(音频包扩展位) #define RTMP_AAC_PACKET_CONFIG 0x00 // AAC配置帧(ADTS头参数) #define RTMP_AAC_PACKET_RAW 0x01 // AAC原始数据帧 /************************** 解析结果结构化定义 **************************/ // 视频包解析结果 typedef struct { int is_h264; // 是否为H.264编码 int is_seq_header; // 是否为AVC序列头(SPS/PPS,必须先解析这个才能解码) int is_key_frame; // 是否为关键帧 uint8_t* raw_data; // 原始H.264数据(去掉RTMP头部后的纯数据) int raw_data_len; // 原始H.264数据长度 } RtmpVideoFrame; // 音频包解析结果 typedef struct { int is_aac; // 是否为AAC编码 int is_config; // 是否为AAC配置帧(必须先解析这个才能解码) uint8_t* raw_data; // 原始AAC数据(去掉RTMP头部后的纯数据) int raw_data_len; // 原始AAC数据长度 int sample_rate; // 采样率(8k/16k/32k/44.1k/48k) int channel; // 声道(1:单声道,2:立体声) int bit_depth; // 位深(8/16位) } RtmpAudioFrame; // 元数据解析结果(onMetaData,包含流的基础信息) typedef struct { double video_width; // 视频宽度 double video_height; // 视频高度 double frame_rate; // 视频帧率 double audio_samplerate; // 音频采样率 double audio_channels; // 音频声道 double duration; // 流总时长(秒,直播流为0) double video_bitrate; // 视频码率(kbps) double audio_bitrate; // 音频码率(kbps) } RtmpMetaData; // 通用RTMP包解析结果 typedef struct { int packet_type; // 包类型:音频/视频/元数据/其他 uint32_t timestamp; // 时间戳(毫秒) uint32_t body_len; // 数据包体长度 RtmpVideoFrame video; // 视频包数据(仅packet_type=视频时有效) RtmpAudioFrame audio; // 音频包数据(仅packet_type=音频时有效) RtmpMetaData meta; // 元数据(仅packet_type=元数据时有效) int is_valid; // 解析是否有效(0:无效,1:有效) } RtmpPacketParseResult; #ifdef __cplusplus extern "C"{ #endif void RtmpParseResult_Init(RtmpPacketParseResult* result); void RtmpParseResult_Free(RtmpPacketParseResult* result); int RTMPPullPacket_Parse(RTMPPullPacket* packet, RtmpPacketParseResult* result); #ifdef __cplusplus } #endif #endif // RTMPPARSE_H

2.rtmpparse.cpp

#include "rtmpparse.h" #include <iostream> /************************** 工具函数:初始化/释放解析结果 **************************/ // 初始化解析结果结构体 void RtmpParseResult_Init(RtmpPacketParseResult* result) { if (result == NULL) return; memset(result, 0, sizeof(RtmpPacketParseResult)); // 初始化默认值 result->is_valid = 0; result->meta.video_width = 0; result->meta.video_height = 0; result->audio.sample_rate = 0; result->audio.channel = 0; } // 释放解析结果的内存(防止内存泄漏) void RtmpParseResult_Free(RtmpPacketParseResult* result) { if (result == NULL) return; // 释放视频原始数据 if (result->video.raw_data != NULL) { free(result->video.raw_data); result->video.raw_data = NULL; } // 释放音频原始数据 if (result->audio.raw_data != NULL) { free(result->audio.raw_data); result->audio.raw_data = NULL; } // 重置结构体 memset(result, 0, sizeof(RtmpPacketParseResult)); } /************************** 核心解析接口:解析RTMPPacket **************************/ /** * @brief 解析RTMP元数据(onMetaData,AMF0格式) * @param body RTMP包体指针 * @param body_len 包体长度 * @param meta 解析后的元数据结果 * @return 0:失败,1:成功 */ static int parse_amf0_metadata(uint8_t* body, int body_len, RtmpMetaData* meta) { #if 0 /************************ 第一步:入参合法性校验 ************************/ if (body == NULL || body_len < 10 || meta == NULL) { printf("【元数据解析】失败:入参无效或包体过短(长度:%d)\n", body_len); return 0; } memset(meta, 0, sizeof(RtmpMetaData)); const char* amf_buf = (const char*)body; int parse_offset = 0; // 定义需要的AVal键名(原生amf.h的AVC宏) const AVal av_onMetaData = AVC("onMetaData"); const AV
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/15 14:30:09

Pi0具身智能在机器人教学中的应用:无需硬件即可体验VLA模型

Pi0具身智能在机器人教学中的应用&#xff1a;无需硬件即可体验VLA模型 元数据框架 标题&#xff1a;Pi0具身智能在机器人教学中的应用&#xff1a;无需硬件即可体验VLA模型关键词&#xff1a;具身智能、VLA模型、机器人教学、Pi0模型、ALOHA机器人、视觉语言动作、Gradio交互…

作者头像 李华
网站建设 2026/6/15 15:03:44

7步精通AI视频合成:ComfyUI-VideoHelperSuite完全指南

7步精通AI视频合成&#xff1a;ComfyUI-VideoHelperSuite完全指南 【免费下载链接】ComfyUI-VideoHelperSuite Nodes related to video workflows 项目地址: https://gitcode.com/gh_mirrors/co/ComfyUI-VideoHelperSuite 在数字内容创作领域&#xff0c;视频节点配置与…

作者头像 李华
网站建设 2026/6/15 14:37:32

Qwen3-Reranker-0.6B部署教程:Kubernetes集群中水平扩缩容实践分享

Qwen3-Reranker-0.6B部署教程&#xff1a;Kubernetes集群中水平扩缩容实践分享 1. 为什么需要语义重排序服务 在构建企业级RAG系统时&#xff0c;你可能已经搭好了向量数据库和大模型推理服务&#xff0c;但很快会遇到一个现实问题&#xff1a;检索返回的前10个文档里&#x…

作者头像 李华
网站建设 2026/6/12 16:46:07

FLUX.1-dev-fp8-dit文生图镜像免配置实战:无需conda/pip,Docker直接运行

FLUX.1-dev-fp8-dit文生图镜像免配置实战&#xff1a;无需conda/pip&#xff0c;Docker直接运行 1. 为什么这次部署特别轻松&#xff1f; 你有没有试过为一个新模型折腾半天环境&#xff1f;装Python版本、配CUDA驱动、解决pip依赖冲突、反复重装torch……最后发现显存还差2G…

作者头像 李华
网站建设 2026/6/15 11:48:08

TegraRcmGUI新手实用指南:轻松掌握Switch注入操作

TegraRcmGUI新手实用指南&#xff1a;轻松掌握Switch注入操作 【免费下载链接】TegraRcmGUI C GUI for TegraRcmSmash (Fuse Gele exploit for Nintendo Switch) 项目地址: https://gitcode.com/gh_mirrors/te/TegraRcmGUI 准备篇&#xff1a;搭建Switch注入环境 安装T…

作者头像 李华
网站建设 2026/6/15 11:44:08

千问图像生成16Bit开源部署指南:Python Flask后端+Diffusers框架详解

千问图像生成16Bit开源部署指南&#xff1a;Python Flask后端Diffusers框架详解 1. 为什么需要BF16图像生成系统&#xff1f; 你有没有遇到过这样的情况&#xff1a;用FP16精度跑图生图模型&#xff0c;明明提示词写得挺清楚&#xff0c;结果生成的图片一半是黑的&#xff0c…

作者头像 李华