ffmpeg中hls.c代码分析

HLS流在播放时是先解协议(hls.c)后解封装(mpegts.c),libavformat下的hls.c和mpegts.c实际上是同一个级别的,同属于demuxer。

一、解HLS协议

1. FFmpeg代码分析    

    首先看一下ff_hls_demuxer的定义:

AVInputFormat ff_hls_demuxer = {
    .name           = "hls,applehttp",
    .long_name      = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming"),
    .priv_class     = &hls_class,
    .priv_data_size = sizeof(HLSContext),
    .read_probe     = hls_probe,
    .read_header    = hls_read_header,
    .read_packet    = hls_read_packet,
    .read_close     = hls_close,
    .read_seek      = hls_read_seek,
};

(1)FFmpeg在拿到hls流后,它一开始并不知道该用哪个demuxer,这个时候它会进行probe,即依次调用demuxer的read_probe函数,选择返回分数最高的一个demuxer,最后选定ff_hls_demuxer。AVFormatContext中的s->iformat->priv_data一般是指demuxer内部的结构体,在解HLS协议时,这个priv_data就是HLSContext;

(2)之后FFmpeg会调用read_header函数,即执行hls_read_header,获取hls的播放列表,并赋值到HLSContext结构体中;

(3)当准备工作妥当后,FFmpeg会调用hls_read_packet读取数据,传递给上层;

(4)如果有seek操作会执行hls_read_seek;

(5)视频关闭时,FFmpeg会调用hls_close;

2. discontinue字段

    FFmpeg3.4没有支持HLS标准的discontinue字段。discontinue字段常用于ts播放列表里插入一段广告,这段广告的参数可以与正片的参数不一致。上层在识别到这个参数后,可以重置解码器参数。

二、解ts封装 

以mpegts.c为例,probe一般是将读取到的probe数据与ts格式对比,如果是ts格式则返回高分数,上层选择最高的分数的demuxer;

PES包:分割打包的ES流,加入了PES头。

struct MpegTSFilter {
    int pid;
    int es_id;
    int last_cc; /* last cc code (-1 if first packet) */
    int64_t last_pcr;
    enum MpegTSFilterType type;
    union {// 一个Filter是下边的一种类型
        MpegTSPESFilter pes_filter;
        MpegTSSectionFilter section_filter;
    } u;
};

handle_packet函数处理一个ts包,在函数中switch case语句中处理ts包。一般是先处理ts header, 之后是pes header,代码中状态定义:

/* TS stream handling */
// 标识TS流状态
enum MpegTSState {
    MPEGTS_HEADER = 0,
    MPEGTS_PESHEADER,
    MPEGTS_PESHEADER_FILL,
    MPEGTS_PAYLOAD,
    MPEGTS_SKIP,
};

 从av_read_frame读到的pkt,其音频和视频的pts是连续的,但是两者之间不是连续的,因为pts乘以时基才是真正的显示时间,比如如下打印日志,pkt时间进行换算后,可以看到其整体pts是连续的。

martinjia time_base:1 30000, pkt index:0, pkt pts:83083(pts换算后:2.76s)
martinjia martinjia time_base:1 48000, pkt index:1, pkt pts:118784(pts换算后:2.47s)

avformat_open_input(http.xxx.m3u8)
    init_input(s, filename, &tmp))
        //提供的文件名信息不能探测格式
        av_probe_input_format2(&pd, 0, &score)))
        io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)
            io_open_default
                ffio_open_whitelist
                    ffurl_alloc
                        //探测是HTTP协议 URLProtocol ff_http_protocol
                        url_find_protocol(filename);  
                    ffurl_connect    //发送HTTP报文头,下载http.xxx.m3u8文件
        //读m3u8文件探测解复用是AVInputFormat *iformat ="hls,applehttp"
        *fmt = av_probe_input_buffer2(s->pb, &s->iformat, filename, s, 0, s->format_probesize);
        s->iformat->read_header(s);  //iformat iformat  hls.c-->hls_read_header
 
 
 
s->iformat->read_header(s);
static int hls_read_header(AVFormatContext *s){
    //解析m3u8文件,把相应字段放在playlist结构体中,playlist结构体就相当于index.m3u8的数组形式
    parse_playlist  
    ffio_init_context(read_data);
     //用read_data读第0片,探测视频文件的解复用是AVInputFormat ff_mpegts_demuxer
    av_probe_input_buffer(&pls->pb, &in_fmt, pls->segments[0]->url, NULL, 0, 0);  
    avformat_open_input(&pls->ctx, pls->segments[0]->url, in_fmt, NULL); //打开第0片视频文件
}
 
s->iformat->read_packet(s, pkt);
static int hls_read_packet(AVFormatContext *s, AVPacket *pkt){
    while(1){
        ret = av_read_frame(pls->ctx, &pls->pkt);
        if(ret >0){
            //没有SEEK的话,读包成功直接退出循环
            if (pls->seek_timestamp == AV_NOPTS_VALUE) break;   
 
            //有SEEK操作
            if (pls->seek_stream_index == pls->pkt.stream_index) { 
                tb = get_timebase(pls);
                ts_diff = av_rescale_rnd(pls->pkt.dts) -  pls->seek_timestamp;
 
                //本片中通过比较pkt.dts,seek_timestamp接近了SEEK点,本次SEEK成功
                if (ts_diff >= 0 && (pls->pkt.flags & AV_PKT_FLAG_KEY)) {
                    pls->seek_timestamp = AV_NOPTS_VALUE;  
                    break;
                }
            }
        }
    }
}
 
 
static int hls_read_seek(AVFormatContext *s, int stream_index,
                               int64_t timestamp, int flags){
    //根据timestamp在playlist查找是第几片
    find_timestamp_in_playlist(c, seek_pls, seek_timestamp, &seq_no) 
    pls->cur_seq_no = seq_no; // read_data会调用current_segment方法,下次下载第seq_no片
    pls->seek_stream_index = stream_subdemuxer_index;
 
    ff_format_io_close(pls->parent, &pls->input); //关闭现在正在下载的片input
    pls->pb.eof_reached = 0;
    /* Clear any buffered data */
    pls->pb.buf_end = pls->pb.buf_ptr = pls->pb.buffer;
    //hls_read_packet 里面会根据这个seek_timestamp判断下载seq_no片中,第多少帧刚好是这个SEEK点
    pls->seek_timestamp = seek_timestamp;  
    pls->seek_flags = flags;
}
 
static int hls_close(AVFormatContext *s)
                avformat_close_input(&pls->ctx);
 
 
static int read_data(void *opaque, uint8_t *buf, int buf_size){ //核心方法
    if (!v->input){ //v->input 判断本片是否打开过
        seg = current_segment(v);
        ret = open_input(c, v, seg);
                //http.aaa.x.ts
                open_url(v->parent, &v->input, seg->url, c->avio_opts, opts, &is_http)
                    avio_find_protocol_name(url);
                    //走HTTP协议,发送HTTP报文头,下载x.ts文件
                    io_open(s, pb, url, AVIO_FLAG_READ, &tmp)
    }
    //读HTTP报文体剩下的x.ts文件字节
    ret = read_from_url(v, current_segment(v), buf, buf_size, READ_NORMAL);
    if (ret > 0){
        return ret;
    }else{ //本片下载完
        ff_format_io_close(v->parent, &v->input);   //v->input = NULL
        v->cur_seq_no++;
    }
}
 
 
 
const URLProtocol ff_http_protocol = {
    .name                = "http",
    .url_open2           = http_open,
    .url_accept          = http_accept,
    .url_handshake       = http_handshake,
    .url_read            = http_read,
    .url_write           = http_write,
    .url_seek            = http_seek,
    .url_close           = http_close,
    .url_get_file_handle = http_get_file_handle,
    .url_shutdown        = http_shutdown,
    .priv_data_size      = sizeof(HTTPContext),
    .priv_data_class     = &http_context_class,
    .flags               = URL_PROTOCOL_FLAG_NETWORK,
    .default_whitelist   = "http,https,tls,rtp,tcp,udp,crypto,httpproxy"
};
 
AVInputFormat ff_hls_demuxer = {
    .name           = "hls,applehttp",
    .long_name      = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming"),
    .priv_class     = &hls_class,
    .priv_data_size = sizeof(HLSContext),
    .read_probe     = hls_probe,
    .read_header    = hls_read_header,
    .read_packet    = hls_read_packet,
    .read_close     = hls_close,
    .read_seek      = hls_read_seek,
};
 
AVInputFormat ff_mpegts_demuxer = {
    .name           = "mpegts",
    .long_name      = NULL_IF_CONFIG_SMALL("MPEG-TS (MPEG-2 Transport Stream)"),
    .priv_data_size = sizeof(MpegTSContext),
    .read_probe     = mpegts_probe,
    .read_header    = mpegts_read_header,
    .read_packet    = mpegts_read_packet,
    .read_close     = mpegts_read_close,
    .read_timestamp = mpegts_get_dts,
    .flags          = AVFMT_SHOW_IDS | AVFMT_TS_DISCONT,
    .priv_class     = &mpegts_class,
};
 
 
 
HTTP报文件:
 
(3671754)request: GET /2017127/5de/529/zehBOsrZo57vbeJy/index.m3u8 HTTP/1.1
User-Agent: Lavf/57.41.100
Accept: */*
Range: bytes=0-
Connection: close
Host: xxxcdn.hls.yyy.tv
Icy-MetaData: 1
[http @ 0x102708000] (3671754)header='HTTP/1.1 206 Partial Content'
[http @ 0x102708000] (3671754)header='Server: Tengine'
[http @ 0x102708000] (3671754)header='Content-Type: application/x-mpegURL'
[http @ 0x102708000] (3671754)header='Content-Length: 36072'
[http @ 0x102708000] (3671754)header='Connection: close'
[http @ 0x102708000] (3671754)header='Date: Thu, 07 Dec 2017 13:20:30 GMT'
[http @ 0x102708000] (3671754)header='x-oss-request-id: 5A29401E5FB1640973AC7221'
[http @ 0x102708000] (3671754)header='Accept-Ranges: bytes'
[http @ 0x102708000] (3671754)header='ETag: "5DBE181ACD4B983E026E19C50BFF23DD"'
[http @ 0x102708000] (3671754)header='Last-Modified: Thu, 07 Dec 2017 13:20:04 GMT'
[http @ 0x102708000] (3671754)header='x-oss-object-type: Normal'
[http @ 0x102708000] (3671754)header='x-oss-hash-crc64ecma: 17784356933318823809'
[http @ 0x102708000] (3671754)header='x-oss-storage-class: Standard'
[http @ 0x102708000] (3671754)header='Content-MD5: Xb4YGs1LmD4CbhnFC/8j3Q=='
[http @ 0x102708000] (3671754)header='x-oss-server-time: 3'
[http @ 0x102708000] (3671754)header='Via: cache25.l2nu20-2[0,200-0,H], cache1.l2nu20-2[0,0], cache8.cn307[0,206-0,H], cache6.cn307[3,0]'
[http @ 0x102708000] (3671754)header='Age: 763084'
[http @ 0x102708000] (3671754)header='X-Cache: HIT TCP_HIT dirn:17:96357170 mlen:-1'
[http @ 0x102708000] (3671754)header='X-Swift-SaveTime: Thu, 07 Dec 2017 15:58:04 GMT'
[http @ 0x102708000] (3671754)header='X-Swift-CacheTime: 2592000'
[http @ 0x102708000] (3671754)header='Content-Range: bytes 0-36071/36072'
[http @ 0x102708000] (3671754)header='access-control-allow-origin: *'
[http @ 0x102708000] (3671754)header='Timing-Allow-Origin: *'
[http @ 0x102708000] (3671754)header='EagleId: 7b81d7ce15134159145938051e'
[http @ 0x102708000] (3671754)header=''
[hls,applehttp @ 0x104007000] Format hls,applehttp probed with size=2048 and score=100
[hls,applehttp @ 0x104007000] Opening 'http://xxxcdn.hls.yyy.tv/2017127/5de/529/zehBOsrZo57vbeJy/0.ts' for reading
[http @ 0x1026275e0] 
(3671754)request: GET /2017127/5de/529/zehBOsrZo57vbeJy/0.ts HTTP/1.1
User-Agent: Lavf/57.41.100
Accept: */*
Connection: close
Host: xxxcdn.hls.yyy.tv
Icy-MetaData: 1
 
 
[http @ 0x1026275e0] (3671754)header='HTTP/1.1 200 OK'
[http @ 0x1026275e0] (3671754)header='Server: Tengine'
[http @ 0x1026275e0] (3671754)header='Content-Type: video/MP2T'
[http @ 0x1026275e0] (3671754)header='Content-Length: 1233656'
[http @ 0x1026275e0] (3671754)header='Connection: close'
[http @ 0x1026275e0] (3671754)header='Date: Thu, 07 Dec 2017 13:20:31 GMT'
[http @ 0x1026275e0] (3671754)header='x-oss-request-id: 5A29401FF43DB73D87B40E71'
[http @ 0x1026275e0] (3671754)header='Accept-Ranges: bytes'
[http @ 0x1026275e0] (3671754)header='ETag: "8C8A9CAC5FC40404D629A2AF19B84A6F"'
[http @ 0x1026275e0] (3671754)header='Last-Modified: Thu, 07 Dec 2017 10:58:38 GMT'
[http @ 0x1026275e0] (3671754)header='x-oss-object-type: Normal'
[http @ 0x1026275e0] (3671754)header='x-oss-hash-crc64ecma: 13525335145074164730'
[http @ 0x1026275e0] (3671754)header='x-oss-storage-class: Standard'
[http @ 0x1026275e0] (3671754)header='Content-MD5: jIqcrF/EBATWKaKvGbhKbw=='
[http @ 0x1026275e0] (3671754)header='x-oss-server-time: 29'
[http @ 0x1026275e0] (3671754)header='Via: cache42.l2nu20-2[0,200-0,H], cache2.l2nu20-2[0,0], cache2.cn307[0,200-0,H], cache8.cn307[3,0]'
[http @ 0x1026275e0] (3671754)header='Age: 763086'
[http @ 0x1026275e0] (3671754)header='X-Cache: HIT TCP_HIT dirn:15:118266541 mlen:-1'
[http @ 0x1026275e0] (3671754)header='X-Swift-SaveTime: Thu, 07 Dec 2017 13:21:58 GMT'
[http @ 0x1026275e0] (3671754)header='X-Swift-CacheTime: 2592000'
[http @ 0x1026275e0] (3671754)header='access-control-allow-origin: *'
[http @ 0x1026275e0] (3671754)header='Timing-Allow-Origin: *'
[http @ 0x1026275e0] (3671754)header='EagleId: 7b81d7d015134159179953085e'
[http @ 0x1026275e0] (3671754)header=''
..........................................
.......................................... //下载0.ts文件
Format mpegts probed with size=2048 and score=50
(3671754)request: GET /2017127/5de/529/zehBOsrZo57vbeJy/1.ts HTTP/1.1
User-Agent: Lavf/57.41.100
Accept: */*
Connection: close
Host: xxxcdn.hls.yyy.tv
Icy-MetaData: 1
[http @ 0x102509500] (3671754)header='HTTP/1.1 200 OK'
[http @ 0x102509500] (3671754)header='Server: Tengine'
[http @ 0x102509500] (3671754)header='Content-Type: video/MP2T'
[http @ 0x102509500] (3671754)header='Content-Length: 1162028'
[http @ 0x102509500] (3671754)header='Connection: close'
[http @ 0x102509500] (3671754)header='Date: Thu, 07 Dec 2017 13:20:31 GMT'
[http @ 0x102509500] (3671754)header='x-oss-request-id: 5A29401F83B4CE0AA7B40025'
[http @ 0x102509500] (3671754)header='Accept-Ranges: bytes'
[http @ 0x102509500] (3671754)header='ETag: "95EFDEADB86482C551F1BCF868ECEA41"'
[http @ 0x102509500] (3671754)header='Last-Modified: Thu, 07 Dec 2017 10:58:49 GMT'
[http @ 0x102509500] (3671754)header='x-oss-object-type: Normal'
[http @ 0x102509500] (3671754)header='x-oss-hash-crc64ecma: 5079365695105828960'
[http @ 0x102509500] (3671754)header='x-oss-storage-class: Standard'
[http @ 0x102509500] (3671754)header='Content-MD5: le/erbhkgsVR8bz4aOzqQQ=='
[http @ 0x102509500] (3671754)header='x-oss-server-time: 33'
[http @ 0x102509500] (3671754)header='Via: cache31.l2nu20-2[0,200-0,H], cache10.l2nu20-2[2,0], cache7.cn307[0,200-0,H], cache3.cn307[28,0]'
[http @ 0x102509500] (3671754)header='Age: 763096'
[http @ 0x102509500] (3671754)header='X-Cache: HIT TCP_HIT dirn:5:256912714 mlen:-1'
[http @ 0x102509500] (3671754)header='X-Swift-SaveTime: Thu, 07 Dec 2017 15:58:06 GMT'
[http @ 0x102509500] (3671754)header='X-Swift-CacheTime: 2592000'
[http @ 0x102509500] (3671754)header='access-control-allow-origin: *'
[http @ 0x102509500] (3671754)header='Timing-Allow-Origin: *'
[http @ 0x102509500] (3671754)header='EagleId: 7b81d7cb15134159275121813e'
[http @ 0x102509500] (3671754)header=''
..........................................
.......................................... //下载1.ts文件

 

FFmpeg是一个开源的跨平台音视频处理工具,可以用于音视频的编解码、转码、推流等操作。下面是使用FFmpeg进行代码推流HLS的简要介绍: 1. 配置FFmpeg环境:首先需要下载并安装FFmpeg,并配置好环境变量,以便在命令行可以直接使用FFmpeg命令。 2. 编写推流代码:使用FFmpeg进行推流需要编写一段代码,可以使用C或者其他支持FFmpeg的编程语言。以下是一个简单的C语言示例: ```c #include <stdio.h> #include <libavformat/avformat.h> int main() { AVFormatContext *fmt_ctx = NULL; AVOutputFormat *out_fmt = NULL; AVStream *out_stream = NULL; AVCodecContext *codec_ctx = NULL; AVCodec *codec = NULL; int ret; // 初始化FFmpeg av_register_all(); // 创建输出上下文 ret = avformat_alloc_output_context2fmt_ctx, NULL, "hls", "output.m3u8"); if (ret < 0) { printf("Failed to create output context\n"); return ret; } // 添加音视频流 out_fmt = fmt_ctx->oformat; out_stream = avformat_new_stream(fmt_ctx, NULL); if (!out_stream) { printf("Failed to create output stream\n"); return -1; } // 设置编码参数 codec_ctx = out_stream->codec; codec_ctx->codec_id = out_fmt->audio_codec; codec_ctx->codec_type = AVMEDIA_TYPE_AUDIO; codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP; // 设置其他参数... // 查找编码器 codec = avcodec_find_encoder(codec_ctx->codec_id); if (!codec) { printf("Failed to find encoder\n"); return -1; } // 打开编码器 ret = avcodec_open2(codec_ctx, codec, NULL); if (ret < 0) { printf("Failed to open encoder\n"); return ret; } // 打开输出文件 ret = avio_open(&fmt_ctx->pb, "output.m3u8", AVIO_FLAG_WRITE); if (ret < 0) { printf("Failed to open output file\n"); return ret; } // 写入文件头 ret = avformat_write_header(fmt_ctx, NULL); if (ret < 0) { printf("Failed to write header\n"); return ret; } // 推流过程... // 写入文件尾 av_write_trailer(fmt_ctx); // 释放资源 avio_close(fmt_ctx->pb); avformat_free_context(fmt_ctx); return 0; } ``` 3. 编译和运行代码:将上述代码保存为一个C文件,使用相应的编译器进行编译,并链接FFmpeg库。然后运行生成的可执行文件即可开始推流。 需要注意的是,上述示例只是一个简单的推流代码框架,具体的推流过程需要根据实际需求进行配置和实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值