如今很是流行直播,相信不少人都跟我同样十分好奇这个技术是如何实现的,正好最近在作一个ffmpeg的项目,发现这个工具很容易就能够作直播,下面来给你们分享下技术要点:html
首先你得编译出ffmpeg运行所需的静态库,这个百度一下有不少内容,这里我就很少说了,建议能够用Github上的一个开源脚原本编译,简单粗暴有效率。git
地址:连接描述github
下载后直接用终端运行build-ffmpeg.sh脚本就好了,大概半个小时就所有编译好了…反正我以为速度还行吧(PS:当初编译Android源码那叫一个慢啊…),如果报错就再来一遍,直到提示成功。数组
视频直播怎么直播呢?大概流程图以下:缓存
1.直播人设备端:从摄像头获取视频流,而后使用rtmp服务提交到服务器服务器
2.服务器端:接收直播人提交的rtmp视频流,并为观看者提供rtmp源多线程
3.观看者:用播放器播放rtmp源的视频.ide
PS:RTMP是Real Time Messaging Protocol(实时消息传输协议)的首字母缩写。该协议基于TCP,是一个协议族,包括RTMP基本协议及RTMPT/RTMPS/RTMPE等多种变种。函数
前期准备:工具
新建一个项目,将全部须要引入的ffmpeg的静态库及其余相关库引入到工程中,配置头文件搜索路径,这一步网上有不少教程就不重复叙述了。
我是用上面脚本编译的最新版,为了后期使用,须要将这些C文件添加到项目:
cmdutils_common_opts.h cmdutils.h及cmdutils.c config.h 在scratch目录下取个对应平台的 ffmpeg_filter.c ffmpeg_opt.c ffmpeg_videotoolbox.c ffmpeg.h及ffmpeg.c
除了config.h文件外,别的文件均在ffmpeg-3.0源码目录中
注意问题:
1.编译会报错,由于ffmpeg.c文件中包含main函数,请将该函数重命名为ffmpeg_main并在ffmpeg.h中添加ffmpeg_main函数的声明.
2.ffmpeg任务完成后会结束进程,而iOS设备都是单进程多线程任务,因此须要将cmdutils.c文件中的exit_program方法中的
exit(ret);
改成结束线程,须要引入#include <pthread.h>
pthread_exit(NULL);
直播端:用ffmpeg库抓取直播人设备的摄像头信息,生成裸数据流stream,注意!!!这里是裸流,裸流意味着什么呢?就是不包含PTS(Presentation Time Stamp。PTS主要用于度量解码后的视频帧何时被显示出来)、DTS(Decode Time Stamp。DTS主要是标识读入内存中的bit流在何时开始送入解码器中进行解码)等信息的数据流,播放器拿到这种流是没法进行播放的.将这个客户端只须要将这个数据流以RTMP协议传到服务器便可。
如何获取摄像头信息:
使用libavdevice库能够打开获取摄像头的输入流,在ffmpeg中获取摄像头的输入流跟打开文件输入流很相似,示例代码:
//打开一个文件:
AVFormatContext *pFormatCtx = avformat_alloc_context(); avformat_open_input(&pFormatCtx, "test.h264",NULL,NULL);
//获取摄像头输入:
AVFormatContext *pFormatCtx = avformat_alloc_context();
//多了查找输入设备的这一步
AVInputFormat *ifmt=av_find_input_format("vfwcap");
//选取vfwcap类型的第一个输入设别做为输入流
avformat_open_input(&pFormatCtx, 0, ifmt,NULL);
如何使用RTMP上传视频流:
使用RTMP上传文件的指令是:
使用ffmpeg.c中的ffmpeg_main方法直接运行该指令便可,示例代码:
NSString *command = @"ffmpeg -re -i temp.h264 -vcodec copy -f flv rtmp://xxx/xxx/livestream"; //根据空格将指令分割为指令数组 NSArray *argv_array=[command_str componentsSeparatedByString:(@" ")]; //将OC对象转换为对应的C对象
int argc=(int)argv_array.count; char** argv=(char**)malloc(sizeof(char*)*argc); for(int i=0;i<argc;i++) { argv[i]=(char*)malloc(sizeof(char)*1024); strcpy(argv[i],[[argv_array objectAtIndex:i] UTF8String]); } //传入指令数及指令数组 ffmpeg_main(argc,argv); //线程已杀死,下方的代码不会执行 ffmpeg -re -i temp.h264 -vcodec copy -f flv rtmp://xxx/xxx/livestream
这行代码就是
-re参数是按照帧率发送,不然ffmpeg会按最高速率发送,那么视频会忽快忽慢,
-i temp.h264是须要上传的裸h264流
-vcoder copy 这段是复制一份不改变源
-f flv rtmp://xxx/xxx/livestream 是指定格式为flv发送到这个url
这里看到输入是裸流或者是文件,可是咱们从摄像头获取到的是直接内存流,这怎么解决呢?
固然是有办法的啦
1.将这串参数中temp.h264参数变为null
2.初始化自定义的AVIOContext,指定自定义的回调函数。示例代码以下:
//AVIOContext中的缓存
unsigned char *aviobuffer=(unsigned char*)av_malloc(32768); AVIOContext *avio=avio_alloc_context(aviobuffer, 32768,0,NULL,read_buffer,NULL,NULL); pFormatCtx->pb=avio; if(avformat_open_input(&pFormatCtx,NULL,NULL,NULL)!=0){ printf("Couldn't open inputstream.(没法打开输入流)\n"); return -1; }
本身写回调函数,从输入源中取数据。示例代码以下:
//Callback
int read_buffer(void opaque, uint8_t buf, int buf_size){
//休眠,不然会一次性所有发送完
if(pkt.stream_index==videoindex){ AVRational time_base=ifmt_ctx->streams[videoindex]->time_base; AVRational time_base_q={1,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); }
//fp_open替换为摄像头输入流 if(!feof(fp_open)){ inttrue_size=fread(buf,1,buf_size,fp_open); return true_size; }else{ return -1; } }
服务端:原谅我一个移动开发不懂服务器端,大概应该是获取直播端上传的视频流再进行广播.因此就略过吧.
播放端:播放端实际上就是一个播放器,能够有不少解决方案,这里提供一种最简单的,由于不少直播软件播放端和客户端都是同一个软件,因此这里直接使用项目中已经有的ffmpeg进行播放简单粗暴又省事.
在Github上有个基于ffmpeg的第三方播放器kxmovie,直接用这个就好.
地址: 连接描述
当你把kxmovie的播放器部分添加到以前作好的上传部分,你会发现报错了......
查找的结果是kxmovie所使用的avpicture_deinterlace方法不存在,我第一个想法就是想办法屏蔽到这个方法,让程序能正常使用,结果......固然不能正常播放视频了,一百度才发现这个方法竟然是去交错,虽然我视频只是不够丰富,可是也知道这个方法确定是不能少的.
没事,只有改源码了.从ffmpeg官方源码库中能够找到这个方法.
地址: 连接描述
发现这个方法在以前的实现中是在avcodec.h中声明是AVPicture的方法,而后在avpicture.c中再调用libavcodec/imgconvert.c这个文件中,也就是说这个方法自己就是属于imgconvert.c的,avpicture.c只是间接调用,查找ffmpeg3.0的imgconvert.c文件,竟然没这个方法,可是官方代码库中是有这个方法的,难道是已经移除了?移除不移除关我毛事,我只想能用,因此简单点直接改avpicture.c
首先添加这几个宏定义
#define deinterlace_line_inplace deinterlace_line_inplace_c #define deinterlace_line deinterlace_line_c #define ff_cropTbl ((uint8_t *)NULL) 而后从网页上复制这几个方法到avpicture.c文件中 static void deinterlace_line_c static void deinterlace_line_inplace_c static void deinterlace_bottom_field static void deinterlace_bottom_field_inplace int avpicture_deinterlace 再在avcodec.h头文件中, avpicture_alloc方法下面添加声明: attribute_deprecated int avpicture_deinterlace(AVPicture *dst, const AVPicture *src, enum AVPixelFormat pix_fmt, int width, int height); 保存后再用终端执行build-ffmpeg.sh脚本编译一次就好了…再次导入项目中kxmovie就不会报错了,播放视频的代码以下: KxMovieViewController *vc = [KxMovieViewController movieViewControllerWithContentPath:path parameters:nil]; [self presentViewController:vc animated:YES completion:nil];
注:其中path能够是以http/rtmp/trsp开始的url