本来是想先写这一篇的,结果写完了之后,测试,竟然推不出去,尴尬,所以赶紧去补了一下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 = "test.flv";char *url = "rtmp://192.168.1.177:1935/live/chen";FILE *fp = NULL;RTMP rtmp = { 0 };uint32_t preTagsize = 0;//packet attributesuint32_t type = 0;uint32_t datalength = 0;uint32_t timestamp = 0;uint32_t streamid = 0;fp = fopen(flv_name, "rb");if(!fp) {RTMP_LogPrintf("Open File LogError.\n");return -1;}/* set log level *///RTMP_LogLevel loglvl = RTMP_Log; // RTMP_LOGALL;//RTMP_LogSetLevel(loglvl);//第一步if (!InitSockets()){RTMP_Log(RTMP_LOGERROR, "Couldn't load sockets support on your platform, exiting!");return -RD_FAILED;}//第二步RTMP_Init(&rtmp);//第三步,通过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");//第五步,申请消息内存RTMPPacket *packet = NULL;packet = (RTMPPacket*)malloc(sizeof(RTMPPacket));// 为包申请了buffer, 实际在内部申请的为nSize + RTMP_MAX_HEADER_SIZERTMPPacket_Alloc(packet, 1024 * 1024);RTMPPacket_Reset(packet);//第六步,发送//jump over FLV Headerfseek(fp, 9, SEEK_SET);         // 跳过FLV header//jump over previousTagSizenfseek(fp, 4, SEEK_CUR);          // 跳过 previousTagSizenpacket->m_nInfoField2 = rtmp.m_stream_id;int continue_sleep_count = 0;uint32_t audio_start_timestamp = 0;uint32_t audio_current_timestamp = 0;int h264_frame_count = 0;uint32_t start_time = 0;uint32_t now_time = 0;start_time = (uint32_t)get_current_time_msec();while(1){if ((((now_time = (uint32_t)get_current_time_msec()) - start_time)<(audio_current_timestamp - audio_start_timestamp))){Sleep(30);if(continue_sleep_count++ > 30){//                RTMP_Log("continue_sleep_count=%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 = 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 = 0;if (!ReadU24(&datalength, fp))   // 负载数据长度{RTMP_Log(RTMP_LOGERROR, "%s(%d) break\n", __FUNCTION__, __LINE__);break;}printf("datalength = %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 != RTMP_PACKET_TYPE_AUDIO && type != RTMP_PACKET_TYPE_VIDEO){RTMP_Log(RTMP_LOGERROR, "unknown type:%d", type);//jump over non_audio and non_video frame,//jump over next previousTagSizen at the same timefseek(fp, datalength + 4, SEEK_CUR);continue;}if (type == RTMP_PACKET_TYPE_AUDIO){if(audio_start_timestamp == 0){audio_start_timestamp = timestamp;start_time = (uint32_t)get_current_time_msec();}if(timestamp <  audio_current_timestamp)    //回绕?做重置{audio_start_timestamp = 0;start_time = (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 = timestamp;}size_t read_len = 0;if ((read_len = fread(packet->m_body, 1, datalength, fp)) != datalength){RTMP_Log(RTMP_LOGERROR, "fread error, read_len:%d, datalength:%d\n", datalength);break;}if(type == RTMP_PACKET_TYPE_AUDIO){packet->m_nChannel = RTMP_AUDIO_CHANNEL;}if(type == RTMP_PACKET_TYPE_VIDEO){packet->m_nChannel = RTMP_VIDEO_CHANNEL;}packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;packet->m_hasAbsTimestamp = 1;packet->m_nTimeStamp = timestamp;packet->m_packetType = type;packet->m_nBodySize = datalength;//pre_frame_time = 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 == RTMP_PACKET_TYPE_VIDEO){h264_frame_count++;if (h264_frame_count % 50 == 0){printf("t = %d, frame_count = %d, time:%ums, aud:%ums\n",timestamp, h264_frame_count,(uint32_t)get_current_time_msec() - start_time,audio_current_timestamp - audio_start_timestamp);}}}//第七步清场,退出if (fp)fclose(fp);RTMP_Close(&rtmp);CleanupSockets();return 0;
}

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

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

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

附其他部分代码:

#include <stdio.h>
#include <librtmp/log.h>
#include "librtmp/rtmp_sys.h"
#include <stdlib.h>
#include <string.h>
#include <stdint.h>#ifdef _WIN32
#include <Windows.h>
#endif#define RD_SUCCESS          0
#define RD_FAILED           1
#define RD_INCOMPLETE       2enum RTMPChannel
{RTMP_NETWORK_CHANNEL = 2,   ///< channel for network-related messages (bandwidth report, ping, etc)RTMP_SYSTEM_CHANNEL,        ///< channel for sending server control messagesRTMP_AUDIO_CHANNEL,         ///< channel for audio dataRTMP_VIDEO_CHANNEL   = 6,   ///< channel for video dataRTMP_SOURCE_CHANNEL  = 8,   ///< channel for a/v invokes
};#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 + (unsigned long long)tv.tv_usec / 1000);
#endif
}/*read 1 byte*/
int ReadU8(uint32_t *u8, FILE*fp)
{if (fread(u8, 1, 1, fp) != 1)return 0;return 1;
}
/*read 2 byte*/
int ReadU16(uint32_t *u16, FILE*fp)
{if (fread(u16, 2, 1, fp) != 1)return 0;*u16 = HTON16(*u16);return 1;
}
/*read 3 byte*/
int ReadU24(uint32_t *u24, FILE*fp)
{if (fread(u24, 3, 1, fp) != 1)return 0;printf("U24 0x%x\n", *u24);*u24 = HTON24(*u24);return 1;
}
/*read 4 byte*/
int ReadU32(uint32_t *u32, FILE*fp)
{if (fread(u32, 4, 1, fp) != 1)return 0;*u32 = 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) != 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) != 1)return 0;*utime = HTONTIME(*utime);return 1;
}// starts sockets
static int InitSockets()
{#ifdef WIN32WORD version;WSADATA wsaData;version = MAKEWORD(1, 1);return (WSAStartup(version, &wsaData) == 0);
#elsereturn TRUE;
#endif
}static inline void CleanupSockets()
{#ifdef WIN32WSACleanup();
#endif
}

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

  1. 【音视频开发系列】盘点音视频直播RTSP/RTMP推流一定会遇到的各种坑,教你快速解决

    聊聊RTSP/RTMP推流那些坑 1.推流架构分析 2.推流缓存队列的设计 3.FFmpeg函数阻塞问题分析 [音视频开发系列]盘点音视频直播一定会遇到的各种坑,教你快速解决 更多精彩内容包括:C/C ...

  2. 音视频开发---ffmpeg rtmp推流

    目录 推流介绍 FFmpeg推流 推流器函数流程图 代码 遗留问题 参考 推流介绍 推流是将输入视频数据推送至流媒体服务器, 输入视频数据可以是本地视频文件(avi,mp4,flv......),也可 ...

  3. Android音视频学习系列(九) — Android端实现rtmp推流

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

  4. Android音视频学习系列(八) — 基于Nginx搭建(rtmp、http)直播服务器

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

  5. Android音视频学习系列(五) — 掌握音频基础知识并使用AudioTrack、OpenSL ES渲染PCM数据

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

  6. 音视频开发:直播推流技术指南

    一.推流架构 推流SDK客户端的模块主要有三个,推流采集端.队列控制模块.推流端.其中每个模块的主要流程如下,本文的主要目的就是拆分推流流程. 1.1 采集端 视频采集:通过Camera采集视频. 音 ...

  7. Android音视频学习系列(七) — 从0~1开发一款Android端播放器(支持多协议网络拉流本地文件)

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

  8. Android音视频学习系列(六) — 掌握视频基础知识并使用OpenGL ES 2.0渲染YUV数据

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

  9. Android音视频学习系列(十) — 基于FFmpeg + OpenSL ES实现音频万能播放器

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

最新文章

  1. IIS7 配置PHP服务器
  2. android监听器在哪里创建,[转载]android开发中创建按钮事件监听器的几种方法
  3. 中国汽车涂料发展的初期
  4. MIGO相关bapi:BAPI_GOODSMVT_CREATE 移动类型314 E
  5. 为了成长,我所做的一些努力!
  6. svn服务器如何导入导出文件,如何导入svn dump备份文件或源代码文件?
  7. Mysql5.7使用DTS增量同步数据到MaxCompute
  8. TCP/IP协议端口大全
  9. 【视频】Vue作者分享:Vue 3.0 进展
  10. 常见的 HTTP 状态代码及原因
  11. 图像视频压缩:深度学习,有一套
  12. 1415-2 计科计高 软件工程博客Github地址汇总-修正版
  13. 信号怎么用matlab分类,使用迁移学习做信号分类
  14. ajax 解析gzip,javascript – 如何让浏览器gunzip一个Ajax获取gziped文本文件?
  15. TransE,知识图谱嵌入(KGE)论文精读
  16. kind安装k8s集群
  17. android siri声波动画,Waver声波效果开源项目:和 Siri 一起学数学
  18. BaoStock:使用python的baostock接口,查询除权除息信息
  19. 口令红包-利用函数计算构建微信小程序的server端
  20. 专访|十年程序员董一凡:生命不息,学习不止

热门文章

  1. .Net Core 3.1实现微信公众号发送模板消息,且跳转微信小程序
  2. 禾川兴推出 Type-c协议芯片 LDR6028
  3. HDU - 5925 D - Coconuts
  4. 接口请求方式及GET和POST的区别
  5. 均匀分布的公交站等车问题
  6. [LeetCode] 365、水壶问题
  7. uniapp根据输入的文本或者地址生成二维码
  8. VTD — 智能驾驶复杂交通场景仿真工具
  9. 教大家苹果iPhone微信分身ios微信双开苹果手机教程
  10. linux u盘文件乱码,Linux系统下U盘汉字乱码的解决