FFMPEG拉流读取模块在远程监控项目最核心的作用是读取UVC摄像头传输的H264码流,并对其码流进行帧的提取,提取完成之后则把数据传输到VDEC解码模块进行解码。而在我们这个项目中,UVC推流的功能由FFMPEG的命令完成。
FFMPEG拉流读取模块的API
在FFMPEG里面提供了一系列的API对码流进行读取和提取帧,下面我们来看看远程监控项目用到了什么API。
FFMPEG读取模块初始化的API
在FFMPEG里面,读取模块的初始化用的是avformat_open_input
int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);
第一个参数:
AVFormatContext的结构体二级指针,这个结构体是FFMPEG非常重要的内部桥梁,结构体内部包含有AVInputFormat、AVOutputFormat、AVCodec、AVStream、AVDictionary 、AVClass等重要结构体。
第二个参数:
url是需要读取的流媒体文件名,这个地址可以是文件如(xxx.mp4、xxx.ts、xxx.flv);也可以是网络流媒体地址,如:(rtmp://192.168.100.22.22:1935/live/01、rtsp://192.168.100.22.22:1935/live/01、udp://192.168.100.66:8080)
第三个参数:
AVInputFormat结构体指针,AVInputFormat是FFMPEG解复用器对象,它表示的是输入文件容器的格式。通常设置为 NULL
,让 FFmpeg 自动检测
第四个参数:
AVDictionary的二级地址,AVDictionary是FFMPEG的字典结构体,它主要是设置FFMPEG内部的参数,如:video_size分辨率、max_delay最大延迟数等,并且以key-value的方式进行存储。options
: 用于传递额外的选项,通常设置为 NULL
。
FFMPEG获取流媒体具体信息的API
在FFMPEG里面,获取流媒体具体信息的API是avformat_find_stream_info
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
第一个参数:
AVFormatContext结构体指针,这个结构体是FFMPEG非常重要的内部桥梁,它的主要功能是获取FFMPEG解封装的数据,如(flv/mp4/ts)等。
第二个参数:
AVDictionary的二级地址,AVDictionary是FFMPEG的字典结构体,它主要是设置FFMPEG内部的参数,如:video_size分辨率、max_delay最大延迟数等,并且以key-value的方式进行存储。
FFMPEG查找最佳匹配的流媒体函数API
这个函数的最主要作用是获取音视频的索引值,就是stream_index。stream_index可以是音频索引也可以是视频索引
int av_find_best_stream(AVFormatContext *ic, enum AVMediaType type,int wanted_stream_nb,int related_stream,AVCodec **decoder_ret,int flags);
第一个参数:AVFormatContext结构体指针,它表示的是输入流媒体的上下文
第二个参数:type类型,这里指的是要查找类型的索引,分别是AVMEDIA_TYPE_VIDEO视频类型、AVMEDIA_TYPE_AUDIO音频类型、AVMEDIA_TYPE_SUBTITLE字幕类型
第三个参数:wanted_stream_nb期望的流媒体索引号,默认是-1
第四个参数:related_stream前一个流媒体索引号,默认-1
第五个参数:返回decoder_ret解码器的指针
第六个参数:flags查找最佳流的标志位,默认是0
返回值:返回最佳的流媒体索引值,也就是音视频的索引值
FFMPEG读取压缩码流并抽取帧数据的API
在FFMPEG里面,使用av_read_frame对压缩裸流进行帧数据的提取,av_read_frame的工作流程如上图,这里重点说一下它的工作流程。H264(或者H265)的裸流数据,一般是以StartCode+NALU(SPS)+StartCode+NALU(PPS)+StartCode+NALU(I)+StartCode+NALU(P)....这种连续的数据表示的,但是对于解码器来说它只能解码一帧单独的数据不能解码连续的数据。
所以此时,av_read_frame就可以把这一连串的码流进行分割。如上图:第一个StartCode+NALU(SPS),第二个StartCode+NALU(PPS),第三个StartCode+NALU(I),第四个StartCode+NALU(P)被一个一个分割出来,并把这一帧帧分割出来的数据传输到解码器。此时解码器才能够正确解码出数据。
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
第一个参数:AVFormatContext结构体指针
第二个参数:AVPacket结构体指针
远程监控项目FFMPEG拉流输入模块初始化的代码实现
读取json文件并解析url地址
是获取摄像头参数和URL地址的实现过程,是通过JSON文件获取URL地址的实现。这个代码非常简单,利用fopen打开json文件FILE *file = fopen("/www/cjson_dir/network_detail.json", "r"), 并调用fread读取里面的内容, 然后用cJSON的API来解析JSON数据,具体的实现是cJSON *json = cJSON_Parse(data), 并获取JSON文件的URL地址, 具体的实现是cJSON *url_address_json = cJSON_GetObjectItem(json, "url_address"); char *network_address = url_address_json->valuestring。经过这几步,就可以获取URL地址了。
avformat_network_init初始化网络配置
/**
* 第二步:
* 初始化网络库,使用网络库时,必须调用此函数非常重要。
*/
av_format_network_init();
用于初始化网络协议。在某些情况下,当你需要通过网络访问音视频资源时,可能需要调用这个函数来确保网络协议的正确初始化.。
avformat_open_input初始化FFMPEG拉流输入模块
/**
* 第三步:
* vformat_open_input,流输入模块
*/
if ((avformat_open_input(&m_pInFmtCtx, network_address, 0, 0)) < 0)
{
printf("文件读取失败\n");
return -1
}
printf("文件读取成功\n");
是调用avformat_open_input打开输入的AVFormatContext结构体指针(大管家婆), 这里面传入三个参数。第一个传参:AVFormatContext结构体指针; 第二个传参: URL地址, 这里直接从json文件读取; 第三个传参:fmt这里填0即可; 第四个传参: options填0即可。经过上面open操作后,就可以初始化输入的AVFormatContext结构体。
avformat_find_stream_info获取输入端的消息
/**
* 第四步:
* 找到流的一些消息,采样格式或者分辨率.....
* 比如获取视频帧率、视频宽高,重新计算最大分析时长,打开解码器解码获取codec数据
*/
// 把大管家婆传进去,因为打开问的时候,大管家婆已经获取到了所有的消息
avformat_find_stream_info(m_pInFmtCtx, nullptr);
printf("流的消息解析完毕\n");
avformat_find_stream_info获取输入模块的信息,这其中包括视频帧率、视频宽高,重新计算最大分析时长,打开解码器解码获取codec数据。
av_find_best_stream查找对应的音视频索引值
/**
* 第五步:
* 通过av_find_best_stream()函数来获取流的索引
* 第二个参数就是代表着要寻找的流
* 比如视频的index是0,音频的index是1,后面可能还有字幕流..
*/
// 把大管家婆传进去,因为打开问的时候,大管家婆已经获取到了所有的消息
int nvideo_index = av_find_best_stream(m_pInFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr);
if (nvideo_index < 0)
{
avformat_free_context(m_pInFmtCtx);
return -1;
}
// 成功获取视频的索引
printf("==> av_find_best_stream!\n");
av_find_best_stream获取音视频的索引值,由于UVC传来的数据只有视频数据,所以只有视频索引。
整体框架