热门标签 | 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
}


推荐阅读
  • 前言--页数多了以后需要指定到某一页(只做了功能,样式没有细调)html ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 本文详细介绍了Java中的输入输出(IO)流,包括其基本概念、分类及应用。IO流是用于在程序和外部资源之间传输数据的一套API。根据数据流动的方向,可以分为输入流(从外部流向程序)和输出流(从程序流向外部)。此外,还涵盖了字节流和字符流的区别及其具体实现。 ... [详细]
  • 在尝试使用C# Windows Forms客户端通过SignalR连接到ASP.NET服务器时,遇到了内部服务器错误(500)。本文将详细探讨问题的原因及解决方案。 ... [详细]
  • 在编译BSP包过程中,遇到了一个与 'gets' 函数相关的编译错误。该问题通常发生在较新的编译环境中,由于 'gets' 函数已被弃用并视为安全漏洞。本文将详细介绍如何通过修改源代码和配置文件来解决这一问题。 ... [详细]
  • 导航栏样式练习:项目实例解析
    本文详细介绍了如何创建一个具有动态效果的导航栏,包括HTML、CSS和JavaScript代码的实现,并附有详细的说明和效果图。 ... [详细]
  • This document outlines the recommended naming conventions for HTML attributes in Fast Components, focusing on readability and consistency with existing standards. ... [详细]
  • 本文探讨了如何优化和正确配置Kafka Streams应用程序以确保准确的状态存储查询。通过调整配置参数和代码逻辑,可以有效解决数据不一致的问题。 ... [详细]
  • 本文介绍如何使用阿里云的fastjson库解析包含时间戳、IP地址和参数等信息的JSON格式文本,并进行数据处理和保存。 ... [详细]
  • 题目Link题目学习link1题目学习link2题目学习link3%%%受益匪浅!-----&# ... [详细]
  • 本文探讨了 C++ 中普通数组和标准库类型 vector 的初始化方法。普通数组具有固定长度,而 vector 是一种可扩展的容器,允许动态调整大小。文章详细介绍了不同初始化方式及其应用场景,并提供了代码示例以加深理解。 ... [详细]
  • 本题探讨如何通过最大流算法解决农场排水系统的设计问题。题目要求计算从水源点到汇合点的最大水流速率,使用经典的EK(Edmonds-Karp)和Dinic算法进行求解。 ... [详细]
  • 不确定性|放入_华为机试题 HJ9提取不重复的整数
    不确定性|放入_华为机试题 HJ9提取不重复的整数 ... [详细]
  • 本文深入探讨了HTTP请求和响应对象的使用,详细介绍了如何通过响应对象向客户端发送数据、处理中文乱码问题以及常见的HTTP状态码。此外,还涵盖了文件下载、请求重定向、请求转发等高级功能。 ... [详细]
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社区 版权所有