本文分析ffmpeg针对hls处理的源码
概述:首先通过http请求获取m3u8文件,通过解析m3u8文件获取切片最早的的ts文件获取常规的AVFormatContext信息。代码主要实现在hls.c文件.
伪代码如下:可以看出针对hls的协议处理主要实现是在avformat_open_input中,具体细节接下来分析。
AVFormatContext *pFormatCtx = nullptr;
char *purl = "http://192.168.27.161:8080/hls/q.m3u8";
int ir = avformat_open_input(&pFormatCtx, purl, nullptr, nullptr);
if (ir != 0)
{
printf("avformat_open_input Failed.\n");
}
首先进行http的连通伪代码如下:首先通过init_input进行m3u8的数据请求。首先通过ffurl_open_whitelist进行tcp连接,然后通过http_connect方法进行get请求。请求成功后接下来则然后通过av_probe_input_buffer2方法进行数据获取。
utils.c
/* Open input file and probe the format if necessary. */
static int init_input(AVFormatContext *s, const char *filename,
AVDictionary **options)
{
if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)
return ret;
eturn av_probe_input_buffer2(s->pb, &s->iformat, filename,
s, 0, s->format_probesize);
}
http.c
static int http_open(URLContext *h, const char *uri, int flags,
AVDictionary **options)
{
ret = http_open_cnx(h, options);
}
static int http_open_cnx(URLContext *h, AVDictionary **options)
{
location_changed = http_open_cnx_internal(h, options);
}
static int http_open_cnx_internal(URLContext *h, AVDictionary **options)
{
//buf=="tcp://192.168.27.161:8080"
err = ffurl_open_whitelist(&s->hd, buf, AVIO_FLAG_READ_WRITE,
&h->interrupt_callback, options,
h->protocol_whitelist, h->protocol_blacklist, h);
//local_path= "/hls/q.m3u8"
err = http_connect(h, path, local_path, hoststr,
auth, proxyauth, &location_changed);
}
static int http_connect(URLContext *h, const char *path, const char *local_path,
const char *hoststr, const char *auth,
const char *proxyauth, int *new_location)
{
//"GET /hls/q.m3u8 HTTP/1.1\r\nUser-Agent: Lavf/57.72.101\r\nAccept: */*\r\nRange: bytes=0-\r\nConnection: close\r\nHost: 192.168.27.161:8080\r\nIcy-MetaData: 1\r\n\r\n"
if ((err = ffurl_write(s->hd, s->buffer, strlen(s->buffer))) < 0)
goto done;
}
利用av_probe_input_buffer2方法进行m3u8文件获取伪代码如下:通过av_probe_input_buffer2获取对应的m3u8文件信息。
int av_probe_input_buffer2(AVIOContext *pb, AVInputFormat **fmt,
const char *filename, void *logctx,
unsigned int offset, unsigned int max_probe_size)
{
if ((ret = avio_read(pb, buf + buf_offset,
probe_size - buf_offset)) < 0) {
}
}
static int http_read(URLContext *h, uint8_t *buf, int size)
{
size = http_read_stream(h, buf, size);
}
接下来则通过m3u8文件进行解析和读取第一个ts文件流伪代码如下:通过parse_playlist将m3u8文件中ts信息进行解析和存储。然后通过av_probe_input_buffer获取第一个ts文件信息.然后获取对应的AVFormatContext.通过第一个信息用于初始化媒体信息。
int avformat_open_input(AVFormatContext **ps, const char *filename,
AVInputFormat *fmt, AVDictionary **options)
{
if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
if ((ret = s->iformat->read_header(s)) < 0)
goto fail;
}
static int hls_read_header(AVFormatContext *s)
{
if ((ret = parse_playlist(c, s->filename, NULL, s->pb)) < 0)
goto fail;
for (i = 0; i < c->n_playlists; i++) {
ret = av_probe_input_buffer(&pls->pb, &in_fmt, pls->segments[0]->url,
NULL, 0, 0);
ret = avformat_open_input(&pls->ctx, pls->segments[0]->url, in_fmt, NULL);
if (ret < 0)
goto fail;
}
}
总结:
通过FFmpeg针对hls的简单分析可知,首先进行http连接,然后获取m3u8文件,然后解析m3u8文件并获取第一个ts流数据信息用于初始化AVFormatContext.