热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

音视频学习(三、rtmp推流)

本来是想先写这一篇的,结果写完了之后,测试,竟然推不出去,尴尬,所以赶紧去补了一下FLV格式的原理࿰

本来是想先写这一篇的,结果写完了之后,测试,竟然推不出去,尴尬,所以赶紧去补了一下FLV格式的原理,因为这个rtmp推流推的就是flv格式,但是顺序还是不变,还是写推流,你们也可以先看FLV格式解析,可能看着有点乏味,但是如果我们带着问题去看的话,就觉得世界变的有趣了。

3.1 rtmp推流

3.1.1 rtmp推流简介

昨天写了一个简单的拉流程序,一共有几个函数,那几个函数我这里就不写,需要看的可以回到上一节看,但是我们可以根据拉流,也能猜测到推流也是几个函数,反正底层实现都交给库了,现在我们先不去研究库,先把东西做出来,下面是推流的几个重要步骤,可以看拉流对比对比:

InitSockets() //还是初始化socket
RTMP_Init(&rtmp); //初始化RTMP
RTMP_SetupURL(&rtmp, url) //设置参数,这个其实跟拉流那个解析url和设置参数是合并了
RTMP_EnableWrite(&rtmp); //这个就是区别,添加了一个写使能
RTMP_Connect(&rtmp, NULL) //建立连接
RTMP_ConnectStream(&rtmp, 0) //建立流连接
RTMPPacket_Alloc(packet, 1024 * 1024); //因为是发送,所以要准备一下发送buff
RTMP_SendPacket(&rtmp, packet, 0); //发送数据
RTMP_Close(&rtmp); //关闭RTMP
CleanupSockets(); //清除socket

还是把拉流的贴出来吧,没有对比就没有伤害:
在这里插入图片描述
是不是看到就个别的差别,所以推流实现起来也简单了。

3.1.2 rtmp推流实现源码

我这个推流的代码只是简单的实现功能,并没有做其他处理,也就是v0版本的,纯做入门能调用库进行推流的,下一节,就会完善推拉流的代码,我们现在先看简单的,不要着急,慢慢来。

int test_rtmp_push()
{char *flv_name &#61; "test.flv";char *url &#61; "rtmp://192.168.1.177:1935/live/chen";FILE *fp &#61; NULL;RTMP rtmp &#61; { 0 };uint32_t preTagsize &#61; 0;//packet attributesuint32_t type &#61; 0;uint32_t datalength &#61; 0;uint32_t timestamp &#61; 0;uint32_t streamid &#61; 0;fp &#61; fopen(flv_name, "rb");if(!fp) {RTMP_LogPrintf("Open File LogError.\n");return -1;}/* set log level *///RTMP_LogLevel loglvl &#61; RTMP_Log; // RTMP_LOGALL;//RTMP_LogSetLevel(loglvl);//第一步if (!InitSockets()){RTMP_Log(RTMP_LOGERROR, "Couldn&#39;t load sockets support on your platform, exiting!");return -RD_FAILED;}//第二步RTMP_Init(&rtmp);//第三步&#xff0c;通过url设置参数if (!RTMP_SetupURL(&rtmp, url)){RTMP_Log(RTMP_LOGERROR, "SetupURL Err\n");CleanupSockets();return -1;}RTMP_Log(RTMP_LOGERROR, "RTMP_SetBufferMS ---------->\n");RTMP_SetBufferMS(&rtmp, 10000);//第四步// RTMP推流需要EnableWriteRTMP_Log(RTMP_LOGERROR, "RTMP_EnableWrite ---------->\n");RTMP_EnableWrite(&rtmp);RTMP_Log(RTMP_LOGERROR, "RTMP_Connect ---------->\n");if (!RTMP_Connect(&rtmp, NULL)){RTMP_Log(RTMP_LOGERROR, "Connect Err\n");CleanupSockets();return -1;}RTMP_Log(RTMP_LOGERROR, "RTMP_ConnectStream ---------->\n");if (!RTMP_ConnectStream(&rtmp, 0)){RTMP_Log(RTMP_LOGERROR, "ConnectStream Err\n");RTMP_Close(&rtmp);CleanupSockets();return -1;}RTMP_Log(RTMP_LOGERROR, "RTMP_ConnectStream ok. ---------->\n");//第五步&#xff0c;申请消息内存RTMPPacket *packet &#61; NULL;packet &#61; (RTMPPacket*)malloc(sizeof(RTMPPacket));// 为包申请了buffer, 实际在内部申请的为nSize &#43; RTMP_MAX_HEADER_SIZERTMPPacket_Alloc(packet, 1024 * 1024);RTMPPacket_Reset(packet);//第六步&#xff0c;发送//jump over FLV Headerfseek(fp, 9, SEEK_SET); // 跳过FLV header//jump over previousTagSizenfseek(fp, 4, SEEK_CUR); // 跳过 previousTagSizenpacket->m_nInfoField2 &#61; rtmp.m_stream_id;int continue_sleep_count &#61; 0;uint32_t audio_start_timestamp &#61; 0;uint32_t audio_current_timestamp &#61; 0;int h264_frame_count &#61; 0;uint32_t start_time &#61; 0;uint32_t now_time &#61; 0;start_time &#61; (uint32_t)get_current_time_msec();while(1){if ((((now_time &#61; (uint32_t)get_current_time_msec()) - start_time)<(audio_current_timestamp - audio_start_timestamp))){Sleep(30);if(continue_sleep_count&#43;&#43; > 30){
// RTMP_Log("continue_sleep_count&#61;%d timeout, time:%ums, aud:%ums\n",
// continue_sleep_count,
// (uint32_t)get_current_time_msec() - start_time,
// audio_current_timestamp - audio_start_timestamp);}continue;}continue_sleep_count &#61; 0;//not quite the same as FLV specif (!ReadU8(&type, fp)) // 读取tag类型{RTMP_Log(RTMP_LOGERROR, "%s(%d) break %d\n", __FUNCTION__, __LINE__, type);break;}datalength &#61; 0;if (!ReadU24(&datalength, fp)) // 负载数据长度{RTMP_Log(RTMP_LOGERROR, "%s(%d) break\n", __FUNCTION__, __LINE__);break;}printf("datalength &#61; %d %d\n", datalength, type);if (!ReadTime(&timestamp, fp)){RTMP_Log(RTMP_LOGERROR, "%s(%d) break\n", __FUNCTION__, __LINE__);break;}if (!ReadU24(&streamid, fp)){RTMP_Log(RTMP_LOGERROR, "%s(%d) break\n", __FUNCTION__, __LINE__);break;}if (type !&#61; RTMP_PACKET_TYPE_AUDIO && type !&#61; RTMP_PACKET_TYPE_VIDEO){RTMP_Log(RTMP_LOGERROR, "unknown type:%d", type);//jump over non_audio and non_video frame&#xff0c;//jump over next previousTagSizen at the same timefseek(fp, datalength &#43; 4, SEEK_CUR);continue;}if (type &#61;&#61; RTMP_PACKET_TYPE_AUDIO){if(audio_start_timestamp &#61;&#61; 0){audio_start_timestamp &#61; timestamp;start_time &#61; (uint32_t)get_current_time_msec();}if(timestamp < audio_current_timestamp) //回绕&#xff1f;做重置{audio_start_timestamp &#61; 0;start_time &#61; (uint32_t)get_current_time_msec();RTMP_Log(RTMP_LOGWARNING, "%s(%d) timestamp rollback %u->%ums\n",__FUNCTION__, __LINE__, audio_current_timestamp, timestamp);}audio_current_timestamp &#61; timestamp;}size_t read_len &#61; 0;if ((read_len &#61; fread(packet->m_body, 1, datalength, fp)) !&#61; datalength){RTMP_Log(RTMP_LOGERROR, "fread error, read_len:%d, datalength:%d\n", datalength);break;}if(type &#61;&#61; RTMP_PACKET_TYPE_AUDIO){packet->m_nChannel &#61; RTMP_AUDIO_CHANNEL;}if(type &#61;&#61; RTMP_PACKET_TYPE_VIDEO){packet->m_nChannel &#61; RTMP_VIDEO_CHANNEL;}packet->m_headerType &#61; RTMP_PACKET_SIZE_MEDIUM;packet->m_hasAbsTimestamp &#61; 1;packet->m_nTimeStamp &#61; timestamp;packet->m_packetType &#61; type;packet->m_nBodySize &#61; datalength;//pre_frame_time &#61; timestamp;if (!RTMP_IsConnected(&rtmp)){RTMP_Log(RTMP_LOGERROR, "rtmp is not connect\n");break;}if (!RTMP_SendPacket(&rtmp, packet, 0)){RTMP_Log(RTMP_LOGERROR, "Send LogError\n");break;}if (!ReadU32(&preTagsize, fp)){RTMP_Log(RTMP_LOGERROR, "%s(%d) break\n", __FUNCTION__, __LINE__);break;}if (type &#61;&#61; RTMP_PACKET_TYPE_VIDEO){h264_frame_count&#43;&#43;;if (h264_frame_count % 50 &#61;&#61; 0){printf("t &#61; %d, frame_count &#61; %d, time:%ums, aud:%ums\n",timestamp, h264_frame_count,(uint32_t)get_current_time_msec() - start_time,audio_current_timestamp - audio_start_timestamp);}}}//第七步清场&#xff0c;退出if (fp)fclose(fp);RTMP_Close(&rtmp);CleanupSockets();return 0;
}

先去掉其他的代码&#xff0c;留下主干&#xff0c;主干是不是发现就是我刚刚说的哪几个函数&#xff0c;所以推流也是如此简单&#xff0c;既然是测试程序&#xff0c;所以我这是读取文件的方式进行推流&#xff0c;test.flv文件是昨天拉流的时候&#xff0c;拉下来的文件&#xff0c;所以今天就再次利用&#xff0c;作为推流的文件&#xff0c;再推回来。

如果还想了解一下细节&#xff0c;比如推流上去的是什么数据&#xff0c;这个就得看看后一篇文章音视频学习&#xff08;四、FLV格式解析&#xff09;&#xff0c;只有看了这个FLV格式之后&#xff0c;才能明白发送的时候去读取的数据&#xff0c;到底读出了什么&#xff0c;FLV格式第一个字节是类型&#xff0c;08是音频&#xff0c;09是视频&#xff0c;然后接下来就是有效数据的长度&#xff0c;之后那两个就是时间戳&#xff0c;我们现在不讨论时间戳&#xff0c;就把头的数据跳过之后&#xff0c;就去取有效的数据&#xff0c;然后打包到packet里面&#xff0c;进行发送&#xff0c;就是这样简单。

我之前为什么一直推流不上去的原因就是&#xff0c;我擅自把读取时间戳的函数去掉了&#xff0c;因为我觉得读取时间戳没有&#xff0c;结果一直推不上去&#xff0c;所以就火急火燎的跑去看了FLV格式&#xff0c;回来才发现&#xff0c;数据偏移出错了&#xff0c;导致每次读到的数据有错&#xff0c;那为什么读取数据会偏移&#xff0c;就是因为少读取了两个时间戳&#xff0c;导致读错&#xff0c;所以一下子就明白了&#xff0c;就改了过来&#xff0c;果不其然就推流成功了。

附其他部分代码&#xff1a;

#include
#include
#include "librtmp/rtmp_sys.h"
#include
#include
#include #ifdef _WIN32
#include
#endif#define RD_SUCCESS 0
#define RD_FAILED 1
#define RD_INCOMPLETE 2enum RTMPChannel
{RTMP_NETWORK_CHANNEL &#61; 2, ///RTMP_SYSTEM_CHANNEL, ///RTMP_AUDIO_CHANNEL, ///RTMP_VIDEO_CHANNEL &#61; 6, ///RTMP_SOURCE_CHANNEL &#61; 8, ///
};#define HTON16(x) ((x>>8&0xff)|(x<<8&0xff00))
#define HTON24(x) ((x>>16&0xff)|(x<<16&0xff0000)|(x&0xff00))
#define HTON32(x) ((x>>24&0xff)|(x>>8&0xff00)|\(x<<8&0xff0000)|(x<<24&0xff000000))
#define HTONTIME(x) ((x>>16&0xff)|(x<<16&0xff0000)|(x&0xff00)|(x&0xff000000))static int64_t get_current_time_msec()
{
#ifdef _WIN32return (int64_t)GetTickCount();
#elsestruct timeval tv;gettimeofday(&tv, NULL);return ((int64_t)tv.tv_sec * 1000 &#43; (unsigned long long)tv.tv_usec / 1000);
#endif
}/*read 1 byte*/
int ReadU8(uint32_t *u8, FILE*fp)
{if (fread(u8, 1, 1, fp) !&#61; 1)return 0;return 1;
}
/*read 2 byte*/
int ReadU16(uint32_t *u16, FILE*fp)
{if (fread(u16, 2, 1, fp) !&#61; 1)return 0;*u16 &#61; HTON16(*u16);return 1;
}
/*read 3 byte*/
int ReadU24(uint32_t *u24, FILE*fp)
{if (fread(u24, 3, 1, fp) !&#61; 1)return 0;printf("U24 0x%x\n", *u24);*u24 &#61; HTON24(*u24);return 1;
}
/*read 4 byte*/
int ReadU32(uint32_t *u32, FILE*fp)
{if (fread(u32, 4, 1, fp) !&#61; 1)return 0;*u32 &#61; HTON32(*u32);return 1;
}
/*read 1 byte,and loopback 1 byte at once*/
int PeekU8(uint32_t *u8, FILE*fp)
{if (fread(u8, 1, 1, fp) !&#61; 1)return 0;fseek(fp, -1, SEEK_CUR);return 1;
}/*read 4 byte and convert to time format*/
int ReadTime(uint32_t *utime, FILE*fp)
{if (fread(utime, 4, 1, fp) !&#61; 1)return 0;*utime &#61; HTONTIME(*utime);return 1;
}// starts sockets
static int InitSockets()
{
#ifdef WIN32WORD version;WSADATA wsaData;version &#61; MAKEWORD(1, 1);return (WSAStartup(version, &wsaData) &#61;&#61; 0);
#elsereturn TRUE;
#endif
}static inline void CleanupSockets()
{
#ifdef WIN32WSACleanup();
#endif
}


推荐阅读
  • MySQL初级篇——字符串、日期时间、流程控制函数的相关应用
    文章目录:1.字符串函数2.日期时间函数2.1获取日期时间2.2日期与时间戳的转换2.3获取年月日、时分秒、星期数、天数等函数2.4时间和秒钟的转换2. ... [详细]
  • 本文详细介绍了Java反射机制的基本概念、获取Class对象的方法、反射的主要功能及其在实际开发中的应用。通过具体示例,帮助读者更好地理解和使用Java反射。 ... [详细]
  • 本文介绍如何使用 Python 的 DOM 和 SAX 方法解析 XML 文件,并通过示例展示了如何动态创建数据库表和处理大量数据的实时插入。 ... [详细]
  • MySQL Decimal 类型的最大值解析及其在数据处理中的应用艺术
    在关系型数据库中,表的设计与SQL语句的编写对性能的影响至关重要,甚至可占到90%以上。本文将重点探讨MySQL中Decimal类型的最大值及其在数据处理中的应用技巧,通过实例分析和优化建议,帮助读者深入理解并掌握这一重要知识点。 ... [详细]
  • 如何将TS文件转换为M3U8直播流:HLS与M3U8格式详解
    在视频传输领域,MP4虽然常见,但在直播场景中直接使用MP4格式存在诸多问题。例如,MP4文件的头部信息(如ftyp、moov)较大,导致初始加载时间较长,影响用户体验。相比之下,HLS(HTTP Live Streaming)协议及其M3U8格式更具优势。HLS通过将视频切分成多个小片段,并生成一个M3U8播放列表文件,实现低延迟和高稳定性。本文详细介绍了如何将TS文件转换为M3U8直播流,包括技术原理和具体操作步骤,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 本文详细介绍了在 Oracle 数据库中使用 MyBatis 实现增删改查操作的方法。针对查询操作,文章解释了如何通过创建字段映射来处理数据库字段风格与 Java 对象之间的差异,确保查询结果能够正确映射到持久层对象。此外,还探讨了插入、更新和删除操作的具体实现及其最佳实践,帮助开发者高效地管理和操作 Oracle 数据库中的数据。 ... [详细]
  • 在Android平台中,播放音频的采样率通常固定为44.1kHz,而录音的采样率则固定为8kHz。为了确保音频设备的正常工作,底层驱动必须预先设定这些固定的采样率。当上层应用提供的采样率与这些预设值不匹配时,需要通过重采样(resample)技术来调整采样率,以保证音频数据的正确处理和传输。本文将详细探讨FFMpeg在音频处理中的基础理论及重采样技术的应用。 ... [详细]
  • 本文深入探讨了CGLIB BeanCopier在Bean对象复制中的应用及其优化技巧。相较于Spring的BeanUtils和Apache的BeanUtils,CGLIB BeanCopier在性能上具有显著优势。通过详细分析其内部机制和使用场景,本文提供了多种优化方法,帮助开发者在实际项目中更高效地利用这一工具。此外,文章还讨论了CGLIB BeanCopier在复杂对象结构和大规模数据处理中的表现,为读者提供了实用的参考和建议。 ... [详细]
  • 兆芯X86 CPU架构的演进与现状(国产CPU系列)
    本文详细介绍了兆芯X86 CPU架构的发展历程,从公司成立背景到关键技术授权,再到具体芯片架构的演进,全面解析了兆芯在国产CPU领域的贡献与挑战。 ... [详细]
  • 本文介绍如何使用线段树解决洛谷 P1531 我讨厌它问题,重点在于单点更新和区间查询最大值。 ... [详细]
  • poj 3352 Road Construction ... [详细]
  • 本文提出了一种基于栈结构的高效四则运算表达式求值方法。该方法能够处理包含加、减、乘、除运算符以及十进制整数和小括号的算术表达式。通过定义和实现栈的基本操作,如入栈、出栈和判空等,算法能够准确地解析并计算输入的表达式,最终输出其计算结果。此方法不仅提高了计算效率,还增强了对复杂表达式的处理能力。 ... [详细]
  • 使用Maven JAR插件将单个或多个文件及其依赖项合并为一个可引用的JAR包
    本文介绍了如何利用Maven中的maven-assembly-plugin插件将单个或多个Java文件及其依赖项打包成一个可引用的JAR文件。首先,需要创建一个新的Maven项目,并将待打包的Java文件复制到该项目中。通过配置maven-assembly-plugin,可以实现将所有文件及其依赖项合并为一个独立的JAR包,方便在其他项目中引用和使用。此外,该方法还支持自定义装配描述符,以满足不同场景下的需求。 ... [详细]
  • 本文介绍了UUID(通用唯一标识符)的概念及其在JavaScript中生成Java兼容UUID的代码实现与优化技巧。UUID是一个128位的唯一标识符,广泛应用于分布式系统中以确保唯一性。文章详细探讨了如何利用JavaScript生成符合Java标准的UUID,并提供了多种优化方法,以提高生成效率和兼容性。 ... [详细]
  • FastDFS Nginx 扩展模块的源代码解析与技术剖析
    FastDFS Nginx 扩展模块的源代码解析与技术剖析 ... [详细]
author-avatar
online168
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有