在FFMPEG中使用libRTMP的经验

FFMPEG在编译的时候可以选择支持RTMP的类库libRTMP。这样ffmpeg就可以支持rtmp://, rtmpt://, rtmpe://, rtmpte://,以及 rtmps://协议了。但是如何使用ffmpeg支持RTMP协议还是有一定的学问的。本文总结一下部分经验。

ffmpeg 接受一个字符串的输入方式,比如:“rtmp://xxxx live=1 playpath=xxx ...”这种的输入形式,即第一个字符串是rtmp的url,然后加一个空格,然后再附加一些参数。附加的参数的格式形如“playpath=xxxx” 这种形式。这个乍一看让人觉得有点不习惯。因为一般的输入只包含一个字符串,比如说一个流媒体的url或者是文件的路径,不会采用“url+空格+参数1+参数2+...”的形式。

例如,当需要打开一个直播流的时候,可以用如下字符串(这里连接的是中国教育电视台1频道(网络直播)):

rtmp://pub1.guoshi.com/live/newcetv1

当需要用ffmpeg保存RTMP直播流媒体的时候:

ffmpeg -i "rtmp://pub1.guoshi.com/live/newcetv1 live=1" -vcodec copy -acodec copy ttt.flv

当需要用ffplay播放RTMP直播流媒体的时候:

ffplay "rtmp://pub1.guoshi.com/live/newcetv1 live=1"

在使用FFMPEG类库进行编程的时候,也是一样的,只需要将字符串传递给avformat_open_input()就行了,形如(这里连接的是香港电视台频道(网络直播)):

char url[]="rtmp://live.hkstv.hk.lxdns.com/live/hks live=1";
avformat_open_input(&pFormatCtx,url,NULL,&avdic)

注:librtmp支持的参数:http://rtmpdump.mplayerhq.hu/librtmp.3.html

基于RTMP的实时流媒体的QoE分析

Holly French等人在论文《Real Time Video QoE Analysis of RTMP Streams》中,研究了基于RTMP的实时视频的QoE。在此记录一下。

他们的研究结果表明,码率(bitrate)与帧率或者带宽结合,可以相对准确的反映RTMP视频流的QoE。

他们的实验设计如下图所示。分析服务器包含质量分析器以及相应的数据库。web服务器提供了显示视频的页面。Flash流媒体服务器是提供视频源。Flash流媒体服务器和客户端之间有一个网络模拟器,可以模拟网络上的丢包和延时。

实验一共有10人参加,平均每人观看10个视频。测试序列如下表所示:

引入的丢包率在0-15%,时延在0-100ms。

实验的结果如下图所示。横坐标为3个测试序列,其中每个序列都通过不同的指标预测RTMP流的QoE。纵坐标为精确度。

从实验的结果来看,对于高清晰度的视频,使用带宽+码率(BW+BR)预测QoE的精确度能达到80%。

对于标准清晰度的视频,使用码率+帧率(BR+FR)或者单独使用码率预测QoE的精确度能达到70%。

最终可以得出结论:码率(bitrate)与帧率或者带宽结合,可以相对准确的反映RTMP视频流的QoE。

最简单的基于FFmpeg的推流器(以推送RTMP为例)

本文记录一个最简单的基于FFmpeg的推流器(simplest ffmpeg streamer)。推流器的作用就是将本地的视频数据推送至流媒体服务器。本文记录的推流器,可以将本地的 MOV / AVI / MKV / MP4 / FLV 等格式的媒体文件,通过流媒体协议(例如RTMP,HTTP,UDP,TCP,RTP等等)以直播流的形式推送出去。由于流媒体协议种类繁多,不一一记录。在这里记录将本地文件以RTMP直播流的形式推送至RTMP流媒体服务器(例如 Flash Media Server,Red5,Wowza等等)的方法。
在这个推流器的基础上可以进行多种方式的修改,实现各式各样的推流器。例如:

* 将输入文件改为网络流URL,可以实现转流器。

* 将输入的文件改为回调函数(内存读取)的形式,可以推送内存中的视频数据。

* 将输入文件改为系统设备(通过libavdevice),同时加上编码的功能,可以实现实时推流器(现场直播)。

PS:本程序并不包含视频转码的功能。

简介

RTMP 推流器(Streamer)的在流媒体系统中的作用可以用下图表示。首先将视频数据以RTMP的形式发送到流媒体服务器端(Server,比如 FMS,Red5,Wowza等),然后客户端(一般为Flash Player)通过访问流媒体服务器就可以收看实时流了。

运行本程序之前需要先运行RTMP流媒体服务器,并在流媒体服务器上建立相应的Application。有关流媒体服务器的操作不在本文的论述范围内,在此不再详述。本程序运行后,即可通过RTMP客户端(例如 Flash Player, FFplay等等)收看推送的直播流。

需要要注意的地方

封装格式

RTMP采用的封装格式是FLV。因此在指定输出流媒体的时候需要指定其封装格式为“flv”。同理,其他流媒体协议也需要指定其封装格式。例如采用UDP推送流媒体的时候,可以指定其封装格式为“mpegts”。

延时

发送流媒体的数据的时候需要延时。不然的话,FFmpeg处理数据速度很快,瞬间就能把所有的数据发送出去,流媒体服务器是接受不了的。因此需要按照视频实际的帧率发送数据。本文记录的推流器在视频帧与帧之间采用了av_usleep()函数休眠的方式来延迟发送。这样就可以按照视频的帧率发送数据了,参考代码如下。

//…
int64_t start_time=av_gettime();
while () {
//…
//Important:Delay
if(pkt.stream_index==videoindex){
AVRational time_base=ifmt_ctx->streams[videoindex]->time_base;
AVRational time_base_q={,AV_TIME_BASE};
int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
int64_t now_time = av_gettime() - start_time;
if (pts_time > now_time)
av_usleep(pts_time - now_time);
}
//…
}
//…
PTS/DTS问题

没有封装格式的裸流(例如H.264裸流)是不包含PTS、DTS这些参数的。在发送这种数据的时候,需要自己计算并写入AVPacket的PTS,DTS,duration等参数。这里还没有深入研究,简单写了一点代码,如下所示。

//FIX:No PTS (Example: Raw H.264)
//Simple Write PTS
if(pkt.pts==AV_NOPTS_VALUE){
//Write PTS
AVRational time_base1=ifmt_ctx->streams[videoindex]->time_base;
//Duration between 2 frames (us)
int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(ifmt_ctx->streams[videoindex]->r_frame_rate);
//Parameters
pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
pkt.dts=pkt.pts;
pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
}

程序流程图

程序的流程图如下图所示。可以看出和《最简单的基于FFMPEG的封装格式转换器(无编解码)》中的封装格式转换器比较类似。它们之间比较明显的区别在于:
1. Streamer输出为URL

2. Streamer包含了延时部分

代码

代码如下。

/**
* 最简单的基于FFmpeg的推流器(推送RTMP)
* Simplest FFmpeg Streamer (Send RTMP)
*
* 雷霄骅 Lei Xiaohua
* leixiaohua1020@126.com
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 本例子实现了推送本地视频至流媒体服务器(以RTMP为例)。
* 是使用FFmpeg进行流媒体推送最简单的教程。
*
* This example stream local media files to streaming media
* server (Use RTMP as example).
* It's the simplest FFmpeg streamer.
*
*/ #include <stdio.h> #define __STDC_CONSTANT_MACROS #ifdef _WIN32
//Windows
extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/mathematics.h"
#include "libavutil/time.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#include <libavutil/mathematics.h>
#include <libavutil/time.h>
#ifdef __cplusplus
};
#endif
#endif int main(int argc, char* argv[])
{
AVOutputFormat *ofmt = NULL;
//输入对应一个AVFormatContext,输出对应一个AVFormatContext
//(Input AVFormatContext and Output AVFormatContext)
AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
AVPacket pkt;
const char *in_filename, *out_filename;
int ret, i;
int videoindex=-;
int frame_index=;
int64_t start_time=;
//in_filename = "cuc_ieschool.mov";
//in_filename = "cuc_ieschool.mkv";
//in_filename = "cuc_ieschool.ts";
//in_filename = "cuc_ieschool.mp4";
//in_filename = "cuc_ieschool.h264";
in_filename = "cuc_ieschool.flv";//输入URL(Input file URL)
//in_filename = "shanghai03_p.h264"; out_filename = "rtmp://localhost/publishlive/livestream";//输出 URL(Output URL)[RTMP]
//out_filename = "rtp://233.233.233.233:6666";//输出 URL(Output URL)[UDP] av_register_all();
//Network
avformat_network_init();
//输入(Input)
if ((ret = avformat_open_input(&ifmt_ctx, in_filename, , )) < ) {
printf( "Could not open input file.");
goto end;
}
if ((ret = avformat_find_stream_info(ifmt_ctx, )) < ) {
printf( "Failed to retrieve input stream information");
goto end;
} for(i=; i<ifmt_ctx->nb_streams; i++)
if(ifmt_ctx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
videoindex=i;
break;
} av_dump_format(ifmt_ctx, , in_filename, ); //输出(Output) avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", out_filename); //RTMP
//avformat_alloc_output_context2(&ofmt_ctx, NULL, "mpegts", out_filename);//UDP if (!ofmt_ctx) {
printf( "Could not create output context\n");
ret = AVERROR_UNKNOWN;
goto end;
}
ofmt = ofmt_ctx->oformat;
for (i = ; i < ifmt_ctx->nb_streams; i++) {
//根据输入流创建输出流(Create output AVStream according to input AVStream)
AVStream *in_stream = ifmt_ctx->streams[i];
AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);
if (!out_stream) {
printf( "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}
//复制AVCodecContext的设置(Copy the settings of AVCodecContext)
ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
if (ret < ) {
printf( "Failed to copy context from input to output stream codec context\n");
goto end;
}
out_stream->codec->codec_tag = ;
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
//Dump Format------------------
av_dump_format(ofmt_ctx, , out_filename, );
//打开输出URL(Open output URL)
if (!(ofmt->flags & AVFMT_NOFILE)) {
ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
if (ret < ) {
printf( "Could not open output URL '%s'", out_filename);
goto end;
}
}
//写文件头(Write file header)
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < ) {
printf( "Error occurred when opening output URL\n");
goto end;
} start_time=av_gettime();
while () {
AVStream *in_stream, *out_stream;
//获取一个AVPacket(Get an AVPacket)
ret = av_read_frame(ifmt_ctx, &pkt);
if (ret < )
break;
//FIX:No PTS (Example: Raw H.264)
//Simple Write PTS
if(pkt.pts==AV_NOPTS_VALUE){
//Write PTS
AVRational time_base1=ifmt_ctx->streams[videoindex]->time_base;
//Duration between 2 frames (us)
int64_t calc_duration=(double)AV_TIME_BASE/av_q2d(ifmt_ctx->streams[videoindex]->r_frame_rate);
//Parameters
pkt.pts=(double)(frame_index*calc_duration)/(double)(av_q2d(time_base1)*AV_TIME_BASE);
pkt.dts=pkt.pts;
pkt.duration=(double)calc_duration/(double)(av_q2d(time_base1)*AV_TIME_BASE);
}
//Important:Delay
if(pkt.stream_index==videoindex){
AVRational time_base=ifmt_ctx->streams[videoindex]->time_base;
AVRational time_base_q={,AV_TIME_BASE};
int64_t pts_time = av_rescale_q(pkt.dts, time_base, time_base_q);
int64_t now_time = av_gettime() - start_time;
if (pts_time > now_time)
av_usleep(pts_time - now_time); } in_stream = ifmt_ctx->streams[pkt.stream_index];
out_stream = ofmt_ctx->streams[pkt.stream_index];
/* copy packet */
//转换PTS/DTS(Convert PTS/DTS)
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
pkt.pos = -;
//Print to Screen
if(pkt.stream_index==videoindex){
printf("Send %8d video frames to output URL\n",frame_index);
frame_index++;
}
//ret = av_write_frame(ofmt_ctx, &pkt);
ret = av_interleaved_write_frame(ofmt_ctx, &pkt); if (ret < ) {
printf( "Error muxing packet\n");
break;
} av_free_packet(&pkt); }
//写文件尾(Write file trailer)
av_write_trailer(ofmt_ctx);
end:
avformat_close_input(&ifmt_ctx);
/* close output */
if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
avio_close(ofmt_ctx->pb);
avformat_free_context(ofmt_ctx);
if (ret < && ret != AVERROR_EOF) {
printf( "Error occurred.\n");
return -;
}
return ;
}

结果

程序开始运行后。截图如下所示。

可以通过网页播放器播放推送的直播流。

例如下图所示,使用Flash Media Server 的Samples文件夹下的videoPlayer播放直播流的截图如下图所示。(直播地址:rtmp://localhost/publishlive/livestream)

此外,也可以通过FFplay这样的客户端播放直播流。

更新-1.1 (2015.2.13)=========================================

这次考虑到了跨平台的要求,调整了源代码。经过这次调整之后,源代码可以在以下平台编译通过:

VC++:打开sln文件即可编译,无需配置。

cl.exe:打开compile_cl.bat即可命令行下使用cl.exe进行编译,注意可能需要按照VC的安装路径调整脚本里面的参数。编译命令如下。

::VS2010 Environment
call "D:\Program Files\Microsoft Visual Studio 10.0\VC\vcvarsall.bat"
::include
@set INCLUDE=include;%INCLUDE%;
::lib
@set LIB=lib;%LIB%;
::compile and link
cl simplest_ffmpeg_streamer.cpp /link avcodec.lib avformat.lib avutil.lib ^
avdevice.lib avfilter.lib postproc.lib swresample.lib swscale.lib /OPT:NOREF

MinGW:MinGW命令行下运行compile_mingw.sh即可使用MinGW的g++进行编译。编译命令如下。

g++ simplest_ffmpeg_streamer.cpp -g -o simplest_ffmpeg_streamer.exe \
-I /usr/local/include -L /usr/local/lib -lavformat -lavcodec -lavutil

GCC:Linux或者MacOS命令行下运行compile_gcc.sh即可使用GCC进行编译。编译命令如下。

gcc simplest_ffmpeg_streamer.cpp -g -o simplest_ffmpeg_streamer.out \
-I /usr/local/include -L /usr/local/lib -lavformat -lavcodec -lavutil

PS:相关的编译命令已经保存到了工程文件夹中

最简单的基于librtmp的示例:接收(RTMP保存为FLV)

本文记录一个基于libRTMP的接收流媒体的程序:Simplest libRTMP Receive。该程序可以将RTMP流保存成本地FLV文件。实际上本文记录的程序就是一个“精简”过的RTMPDump。RTMPDump功能比较多,因而其代码比较复杂导致很多初学者不知从何下手。而本文记录的这个程序只保留了RTMPDump中最核心的函数,更加方便新手入门学习 libRTMP。

流程图

使用librtmp接收RTMP流的函数执行流程图如下图所示。

流程图中关键函数的作用如下所列:
InitSockets():初始化Socket

RTMP_Alloc():为结构体“RTMP”分配内存。

RTMP_Init():初始化结构体“RTMP”中的成员变量。

RTMP_SetupURL():设置输入的RTMP连接的URL。

RTMP_Connect():建立RTMP连接,创建一个RTMP协议规范中的NetConnection。

RTMP_ConnectStream():创建一个RTMP协议规范中的NetStream。

RTMP_Read():从服务器读取数据。

RTMP_Close():关闭RTMP连接。

RTMP_Free():释放结构体“RTMP”。

CleanupSockets():关闭Socket。

其中NetStream和NetConnection是RTMP协议规范中的两个逻辑结构。NetStream建立在NetConnection之上。一个NetConnection可以包含多个NetStream。它们之间的关系如下图所示。

源代码

/**
* Simplest Librtmp Receive
*
* 雷霄骅,张晖
* leixiaohua1020@126.com
* zhanghuicuc@gmail.com
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 本程序用于接收RTMP流媒体并在本地保存成FLV格式的文件。
* This program can receive rtmp live stream and save it as local flv file.
*/
#include <stdio.h>
#include "librtmp/rtmp_sys.h"
#include "librtmp/log.h" int InitSockets()
{
WORD version;
WSADATA wsaData;
version = MAKEWORD(, );
return (WSAStartup(version, &wsaData) == );
} void CleanupSockets()
{
WSACleanup();
} int main(int argc, char* argv[])
{
InitSockets(); double duration=-;
int nRead;
//is live stream ?
bool bLiveStream=true; int bufsize=**;
char *buf=(char*)malloc(bufsize);
memset(buf,,bufsize);
long countbufsize=; FILE *fp=fopen("receive.flv","wb");
if (!fp){
RTMP_LogPrintf("Open File Error.\n");
CleanupSockets();
return -;
} /* set log level */
//RTMP_LogLevel loglvl=RTMP_LOGDEBUG;
//RTMP_LogSetLevel(loglvl); RTMP *rtmp=RTMP_Alloc();
RTMP_Init(rtmp);
//set connection timeout,default 30s
rtmp->Link.timeout=;
// HKS's live URL
if(!RTMP_SetupURL(rtmp,"rtmp://live.hkstv.hk.lxdns.com/live/hks"))
{
RTMP_Log(RTMP_LOGERROR,"SetupURL Err\n");
RTMP_Free(rtmp);
CleanupSockets();
return -;
}
if (bLiveStream){
rtmp->Link.lFlags|=RTMP_LF_LIVE;
} //1hour
RTMP_SetBufferMS(rtmp, *); if(!RTMP_Connect(rtmp,NULL)){
RTMP_Log(RTMP_LOGERROR,"Connect Err\n");
RTMP_Free(rtmp);
CleanupSockets();
return -;
} if(!RTMP_ConnectStream(rtmp,)){
RTMP_Log(RTMP_LOGERROR,"ConnectStream Err\n");
RTMP_Close(rtmp);
RTMP_Free(rtmp);
CleanupSockets();
return -;
} while(nRead=RTMP_Read(rtmp,buf,bufsize)){
fwrite(buf,,nRead,fp); countbufsize+=nRead;
RTMP_LogPrintf("Receive: %5dByte, Total: %5.2fkB\n",nRead,countbufsize*1.0/);
} if(fp)
fclose(fp); if(buf){
free(buf);
} if(rtmp){
RTMP_Close(rtmp);
RTMP_Free(rtmp);
CleanupSockets();
rtmp=NULL;
}
return ;
}

运行结果

程序运行后,会将URL为“rtmp://live.hkstv.hk.lxdns.com/live/hks”的直播流(实际上是香港卫视)在本地保存为“receive.flv”。保存后的文件使用播放器就可以观看。

最简单的基于librtmp的示例:发布(FLV通过RTMP发布)

本文记录一个基于libRTMP的发布流媒体的程序:Simplest libRTMP Send FLV。该程序可以将本地FLV文件发布到RTMP流媒体服务器。是最简单的基于libRTMP的流媒体发布示例。

流程图

使用librtmp发布RTMP流的可以使用两种API:RTMP_SendPacket()和RTMP_Write()。使用 RTMP_SendPacket()发布流的时候的函数执行流程图如下图所示。使用RTMP_Write()发布流的时候的函数执行流程图相差不大。

流程图中关键函数的作用如下所列:

InitSockets():初始化Socket

RTMP_Alloc():为结构体“RTMP”分配内存。

RTMP_Init():初始化结构体“RTMP”中的成员变量。

RTMP_SetupURL():设置输入的RTMP连接的URL。

RTMP_EnableWrite():发布流的时候必须要使用。如果不使用则代表接收流。

RTMP_Connect():建立RTMP连接,创建一个RTMP协议规范中的NetConnection。

RTMP_ConnectStream():创建一个RTMP协议规范中的NetStream。

Delay:发布流过程中的延时,保证按正常播放速度发送数据。

RTMP_SendPacket():发送一个RTMP数据RTMPPacket。

RTMP_Close():关闭RTMP连接。

RTMP_Free():释放结构体“RTMP”。

CleanupSockets():关闭Socket。

源代码

源代码中包含了使用两种API函数RTMP_SendPacket()和RTMP_Write()发布流媒体的源代码,如下所示。

/**
* Simplest Librtmp Send FLV
*
* 雷霄骅,张晖
* leixiaohua1020@126.com
* zhanghuicuc@gmail.com
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 本程序用于将FLV格式的视音频文件使用RTMP推送至RTMP流媒体服务器。
* This program can send local flv file to net server as a rtmp live stream.
*/ #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#ifndef WIN32
#include <unistd.h>
#endif #include "librtmp/rtmp_sys.h"
#include "librtmp/log.h" #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<<&0xff0000)|(x<<&0xff000000))
#define HTONTIME(x) ((x>>16&0xff)|(x<<16&0xff0000)|(x&0xff00)|(x&0xff000000)) /*read 1 byte*/
int ReadU8(uint32_t *u8,FILE*fp){
if(fread(u8,,,fp)!=)
return ;
return ;
}
/*read 2 byte*/
int ReadU16(uint32_t *u16,FILE*fp){
if(fread(u16,,,fp)!=)
return ;
*u16=HTON16(*u16);
return ;
}
/*read 3 byte*/
int ReadU24(uint32_t *u24,FILE*fp){
if(fread(u24,,,fp)!=)
return ;
*u24=HTON24(*u24);
return ;
}
/*read 4 byte*/
int ReadU32(uint32_t *u32,FILE*fp){
if(fread(u32,,,fp)!=)
return ;
*u32=HTON32(*u32);
return ;
}
/*read 1 byte,and loopback 1 byte at once*/
int PeekU8(uint32_t *u8,FILE*fp){
if(fread(u8,,,fp)!=)
return ;
fseek(fp,-,SEEK_CUR);
return ;
}
/*read 4 byte and convert to time format*/
int ReadTime(uint32_t *utime,FILE*fp){
if(fread(utime,,,fp)!=)
return ;
*utime=HTONTIME(*utime);
return ;
} int InitSockets()
{
WORD version;
WSADATA wsaData;
version=MAKEWORD(,);
return (WSAStartup(version, &wsaData) == );
} void CleanupSockets()
{
WSACleanup();
} //Publish using RTMP_SendPacket()
int publish_using_packet(){
RTMP *rtmp=NULL;
RTMPPacket *packet=NULL;
uint32_t start_time=;
uint32_t now_time=;
//the timestamp of the previous frame
long pre_frame_time=;
long lasttime=;
int bNextIsKey=;
uint32_t preTagsize=; //packet attributes
uint32_t type=;
uint32_t datalength=;
uint32_t timestamp=;
uint32_t streamid=; FILE*fp=NULL;
fp=fopen("cuc_ieschool.flv","rb");
if (!fp){
RTMP_LogPrintf("Open File Error.\n");
CleanupSockets();
return -;
} /* set log level */
//RTMP_LogLevel loglvl=RTMP_LOGDEBUG;
//RTMP_LogSetLevel(loglvl); if (!InitSockets()){
RTMP_LogPrintf("Init Socket Err\n");
return -;
} rtmp=RTMP_Alloc();
RTMP_Init(rtmp);
//set connection timeout,default 30s
rtmp->Link.timeout=;
if(!RTMP_SetupURL(rtmp,"rtmp://localhost/publishlive/livestream"))
{
RTMP_Log(RTMP_LOGERROR,"SetupURL Err\n");
RTMP_Free(rtmp);
CleanupSockets();
return -;
} //if unable,the AMF command would be 'play' instead of 'publish'
RTMP_EnableWrite(rtmp); if (!RTMP_Connect(rtmp,NULL)){
RTMP_Log(RTMP_LOGERROR,"Connect Err\n");
RTMP_Free(rtmp);
CleanupSockets();
return -;
} if (!RTMP_ConnectStream(rtmp,)){
RTMP_Log(RTMP_LOGERROR,"ConnectStream Err\n");
RTMP_Close(rtmp);
RTMP_Free(rtmp);
CleanupSockets();
return -;
} packet=(RTMPPacket*)malloc(sizeof(RTMPPacket));
RTMPPacket_Alloc(packet,*);
RTMPPacket_Reset(packet); packet->m_hasAbsTimestamp = ;
packet->m_nChannel = 0x04;
packet->m_nInfoField2 = rtmp->m_stream_id; RTMP_LogPrintf("Start to send data ...\n"); //jump over FLV Header
fseek(fp,,SEEK_SET);
//jump over previousTagSizen
fseek(fp,,SEEK_CUR);
start_time=RTMP_GetTime();
while()
{
if((((now_time=RTMP_GetTime())-start_time)
<(pre_frame_time)) && bNextIsKey){
//wait for 1 sec if the send process is too fast
//this mechanism is not very good,need some improvement
if(pre_frame_time>lasttime){
RTMP_LogPrintf("TimeStamp:%8lu ms\n",pre_frame_time);
lasttime=pre_frame_time;
}
Sleep();
continue;
} //not quite the same as FLV spec
if(!ReadU8(&type,fp))
break;
if(!ReadU24(&datalength,fp))
break;
if(!ReadTime(×tamp,fp))
break;
if(!ReadU24(&streamid,fp))
break; if (type!=0x08&&type!=0x09){
//jump over non_audio and non_video frame,
//jump over next previousTagSizen at the same time
fseek(fp,datalength+,SEEK_CUR);
continue;
} if(fread(packet->m_body,,datalength,fp)!=datalength)
break; packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
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,)){
RTMP_Log(RTMP_LOGERROR,"Send Error\n");
break;
} if(!ReadU32(&preTagsize,fp))
break; if(!PeekU8(&type,fp))
break;
if(type==0x09){
if(fseek(fp,,SEEK_CUR)!=)
break;
if(!PeekU8(&type,fp)){
break;
}
if(type==0x17)
bNextIsKey=;
else
bNextIsKey=; fseek(fp,-,SEEK_CUR);
}
} RTMP_LogPrintf("\nSend Data Over\n"); if(fp)
fclose(fp); if (rtmp!=NULL){
RTMP_Close(rtmp);
RTMP_Free(rtmp);
rtmp=NULL;
}
if (packet!=NULL){
RTMPPacket_Free(packet);
free(packet);
packet=NULL;
} CleanupSockets();
return ;
} //Publish using RTMP_Write()
int publish_using_write(){
uint32_t start_time=;
uint32_t now_time=;
uint32_t pre_frame_time=;
uint32_t lasttime=;
int bNextIsKey=;
char* pFileBuf=NULL; //read from tag header
uint32_t type=;
uint32_t datalength=;
uint32_t timestamp=; RTMP *rtmp=NULL; FILE*fp=NULL;
fp=fopen("cuc_ieschool.flv","rb");
if (!fp){
RTMP_LogPrintf("Open File Error.\n");
CleanupSockets();
return -;
} /* set log level */
//RTMP_LogLevel loglvl=RTMP_LOGDEBUG;
//RTMP_LogSetLevel(loglvl); if (!InitSockets()){
RTMP_LogPrintf("Init Socket Err\n");
return -;
} rtmp=RTMP_Alloc();
RTMP_Init(rtmp);
//set connection timeout,default 30s
rtmp->Link.timeout=;
if(!RTMP_SetupURL(rtmp,"rtmp://localhost/publishlive/livestream"))
{
RTMP_Log(RTMP_LOGERROR,"SetupURL Err\n");
RTMP_Free(rtmp);
CleanupSockets();
return -;
} RTMP_EnableWrite(rtmp);
//1hour
RTMP_SetBufferMS(rtmp, *);
if (!RTMP_Connect(rtmp,NULL)){
RTMP_Log(RTMP_LOGERROR,"Connect Err\n");
RTMP_Free(rtmp);
CleanupSockets();
return -;
} if (!RTMP_ConnectStream(rtmp,)){
RTMP_Log(RTMP_LOGERROR,"ConnectStream Err\n");
RTMP_Close(rtmp);
RTMP_Free(rtmp);
CleanupSockets();
return -;
} printf("Start to send data ...\n"); //jump over FLV Header
fseek(fp,,SEEK_SET);
//jump over previousTagSizen
fseek(fp,,SEEK_CUR);
start_time=RTMP_GetTime();
while()
{
if((((now_time=RTMP_GetTime())-start_time)
<(pre_frame_time)) && bNextIsKey){
//wait for 1 sec if the send process is too fast
//this mechanism is not very good,need some improvement
if(pre_frame_time>lasttime){
RTMP_LogPrintf("TimeStamp:%8lu ms\n",pre_frame_time);
lasttime=pre_frame_time;
}
Sleep();
continue;
} //jump over type
fseek(fp,,SEEK_CUR);
if(!ReadU24(&datalength,fp))
break;
if(!ReadTime(×tamp,fp))
break;
//jump back
fseek(fp,-,SEEK_CUR); pFileBuf=(char*)malloc(+datalength+);
memset(pFileBuf,,+datalength+);
if(fread(pFileBuf,,+datalength+,fp)!=(+datalength+))
break; pre_frame_time=timestamp; if (!RTMP_IsConnected(rtmp)){
RTMP_Log(RTMP_LOGERROR,"rtmp is not connect\n");
break;
}
if (!RTMP_Write(rtmp,pFileBuf,+datalength+)){
RTMP_Log(RTMP_LOGERROR,"Rtmp Write Error\n");
break;
} free(pFileBuf);
pFileBuf=NULL; if(!PeekU8(&type,fp))
break;
if(type==0x09){
if(fseek(fp,,SEEK_CUR)!=)
break;
if(!PeekU8(&type,fp)){
break;
}
if(type==0x17)
bNextIsKey=;
else
bNextIsKey=;
fseek(fp,-,SEEK_CUR);
}
} RTMP_LogPrintf("\nSend Data Over\n"); if(fp)
fclose(fp); if (rtmp!=NULL){
RTMP_Close(rtmp);
RTMP_Free(rtmp);
rtmp=NULL;
} if(pFileBuf){
free(pFileBuf);
pFileBuf=NULL;
} CleanupSockets();
return ;
} int main(int argc, char* argv[]){
//2 Methods:
publish_using_packet();
//publish_using_write();
return ;
}

运行结果

程序运行后,会将“cuc_ieschool.flv”文件以直播流的形式发布到“rtmp://localhost/publishlive/livestream”的URL。修改文件名称和RTMP的URL可以实现将任意flv文件发布到任意RTMP的URL。

最简单的基于librtmp的示例:发布H.264(H.264通过RTMP发布)

本文记录一个基于libRTMP的发布H.264码流的程序。该程序可以将H.264数据发布到RTMP流媒体服务器。目前这个例子还不是很稳定,下一步还有待修改。

本程序使用回调函数作为输入,通过自定义的回调函数,可以发送本地的文件或者内存中的数据。

函数调用结构图

本程序的函数调用结构图如下所示。

整个程序包含3个接口函数:
RTMP264_Connect():建立RTMP连接。

RTMP264_Send():发送数据。

RTMP264_Close():关闭RTMP连接。

按照顺序调用上述3个接口函数就可以完成H.264码流的发送。

结构图中关键函数的作用如下所列。

RTMP264_Connect()中包含以下函数:

InitSockets():初始化Socket

RTMP_Alloc():为结构体“RTMP”分配内存。

RTMP_Init():初始化结构体“RTMP”中的成员变量。

RTMP_SetupURL():设置输入的RTMP连接的URL。

RTMP_EnableWrite():发布流的时候必须要使用。如果不使用则代表接收流。

RTMP_Connect():建立RTMP连接,创建一个RTMP协议规范中的NetConnection。

RTMP_ConnectStream():创建一个RTMP协议规范中的NetStream。

RTMP264_Send()中包含以下函数:

ReadFirstNaluFromBuf():从内存中读取出第一个NAL单元。

ReadOneNaluFromBuf():从内存中读取出一个NAL单元。

h264_decode_sps():解码SPS,获取视频的宽,高,帧率信息。

SendH264Packet():发送一个NAL单元。

SendH264Packet()中包含以下函数:

SendVideoSpsPps():如果是关键帧,则在发送该帧之前先发送SPS和PPS。

SendPacket():组装一个RTMPPacket,调用RTMP_SendPacket()发送出去。

RTMP_SendPacket():发送一个RTMP数据RTMPPacket。

RTMP264_Close()中包含以下函数:

RTMP_Close():关闭RTMP连接。

RTMP_Free():释放结构体“RTMP”。

CleanupSockets():关闭Socket。

源代码

程序提供的3个接口函数的使用方法如下。可以看出接口比较简单。

/**
* Simplest Librtmp Send 264
*
* 雷霄骅,张晖
* leixiaohua1020@126.com
* zhanghuicuc@gmail.com
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 本程序用于将内存中的H.264数据推送至RTMP流媒体服务器。
* This program can send local h264 stream to net server as rtmp live stream.
*/ #include <stdio.h>
#include "librtmp_send264.h" FILE *fp_send1; //读文件的回调函数
//we use this callback function to read data from buffer
int read_buffer1(unsigned char *buf, int buf_size ){
if(!feof(fp_send1)){
int true_size=fread(buf,,buf_size,fp_send1);
return true_size;
}else{
return -;
}
} int main(int argc, char* argv[])
{
fp_send1 = fopen("cuc_ieschool.h264", "rb"); //初始化并连接到服务器
RTMP264_Connect("rtmp://localhost/publishlive/livestream"); //发送
RTMP264_Send(read_buffer1); //断开连接并释放相关资源
RTMP264_Close(); return ;
}

接口函数内部的代码比较多,不再详细记录。

转:RTMPdump使用相关的更多相关文章

  1. 嵌入式单片机STM32应用技术(课本)

    目录SAIU R20 1 6 第1页第1 章. 初识STM32..................................................................... ...

  2. 转:RTMPDump源代码分析

    0: 主要函数调用分析 rtmpdump 是一个用来处理 RTMP 流媒体的开源工具包,支持 rtmp://, rtmpt://, rtmpe://, rtmpte://, and rtmps://. ...

  3. rtmpdump代码分析 转

    RTMPdump 源代码分析 1: main()函数 rtmpdump 是一个用来处理 RTMP 流媒体的工具包,支持 rtmp://, rtmpt://, rtmpe://, rtmpte://, ...

  4. RTMPdump(libRTMP) 源代码分析 8: 发送消息(Message)

    ===================================================== RTMPdump(libRTMP) 源代码分析系列文章: RTMPdump 源代码分析 1: ...

  5. java中的字符串相关知识整理

    字符串为什么这么重要 写了多年java的开发应该对String不陌生,但是我却越发觉得它陌生.每学一门编程语言就会与字符串这个关键词打不少交道.看来它真的很重要. 字符串就是一系列的字符组合的串,如果 ...

  6. SQL Server相关书籍

    SQL Server相关书籍 (排名不分先后) Microsoft SQL Server 企业级平台管理实践 SQL Server 2008数据库技术内幕 SQL Server性能调优实战 SQL S ...

  7. dotNET跨平台相关文档整理

    一直在从事C#开发的相关技术工作,从C# 1.0一路用到现在的C# 6.0, 通常情况下被局限于Windows平台,Mono项目把我们C#程序带到了Windows之外的平台,在工作之余花了很多时间在M ...

  8. 在ASP.NET Core应用中如何设置和获取与执行环境相关的信息?

    HostingEnvironment是承载应用当前执行环境的描述,它是对所有实现了IHostingEnvironment接口的所有类型以及对应对象的统称.如下面的代码片段所示,一个HostingEnv ...

  9. virtualbox linux虚拟机相关

    linux虚拟机设置为静态IP 在virtualbox中安装好linux虚拟机后,如果采用的是NAT方式的话,linux虚拟机默认采用dhcp方式自动上网,而且用的是NetworkManager服务而 ...

随机推荐

  1. Python学习笔记- Python threading模块

    Python threading模块 直接调用 # !/usr/bin/env python # -*- coding:utf-8 -*- import threading import time d ...

  2. python学习笔记-Day6(3)

    代码书写原则: 1)不能重复写代码 2)写的代码要经常变更 编程模式概述 面向过程:根据业务逻辑从上到下写垒代码 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可 面向对象:对函数 ...

  3. IOS单例模式要做到3点

    1,永远只分配一块内存来创建对象. +(instanst) static id instace = nil; static dispatch_once_t onceToken; dispatch_on ...

  4. C#字段中加入list<类字段> 的两种写法

    类1 public class NumCon { public string zsNum { get; set; } } 类2 public class RepeatMess //重复数据响应 { p ...

  5. [Chapter 3 Process]Practice 3.3 Discuss three major complications that concurrent processing adds to an operating system.

    3.3  Original version of Apple's mobile iOS operating system provied no means of concurrent processi ...

  6. js学习---常用的内置对象(API)小结 :

    内置对象(API): 日期 Date: getFullYear() 返回完整的4位的年份  如:2016 getMonth()    返回月份,从0开始 getDate()   返回当前月的第几天,当 ...

  7. CSS中父元素高度没有随子元素高度的改变而改变,应该如何解决?

    如果子元素没有设置浮动(float),父元素实际上会根据内容,自动宽高进行适应的. 当子元素增加了浮动后,最简单的处理方法是给父元素添加overflow:hidden属性,此时父元素的高度会随子元素的 ...

  8. C# Delegate 匿名 Delegate

    C#6.0新添加了 lambda的强力支持,用lambda的确可以节省好多代码,让代码看起来更简洁,更直观: 这里做一个笔记,C#的匿名委托 Demo class Program { static v ...

  9. MVVM了解

    了解WPF要有两年,零零碎碎也做了几个项目,之前面试的时候面试官必问对MVVM的了解. 其实不太了解,只是做项目的时候一直采用这种模式,Model-View-ViewModel.以下是我在了解过程中的 ...

  10. webdriver 获取元素焦点方法

    --------------------------------------- http://www.ltesting.net/ceshi/open/kygncsgj/selenium/2013/01 ...