2026-08-13 11:28:40 +08:00
/*
* Apple HTTP Live Streaming demuxer
* Copyright (c) 2010 Martin Storsjo
* Copyright (c) 2013 Anssi Hannula
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file
* Apple HTTP Live Streaming demuxer
* http://tools.ietf.org/html/draft-pantos-http-live-streaming
*/
#include "aml/hls/ffhls.h"
#include "avformat.h"
#include "aviolpbuf.h"
#include "ffhls.h"
#include "libavformat/avformat.h"
#include "libavformat/avio_internal.h"
#include "libavutil/base64.h"
#include <sys/time.h>
#include "get_config.h"
#include "libavformat/aml/tcpproto/dns_cache.h"
#include "libavformat/mpegts.h"
#include "libavutil/encryption_info.h"
#include <amconfigutils.h>
static int reopen_pls ( AVFormatContext * s , struct playlist * pls );
static void free_segment_list ( struct playlist * pls )
{
int i ;
for ( i = 0 ; i < pls -> n_segments ; i ++ ) {
av_freep ( & pls -> segments [ i ] -> key );
av_freep ( & pls -> segments [ i ] -> url );
av_freep ( & pls -> segments [ i ]);
}
av_freep ( & pls -> segments );
pls -> n_segments = 0 ;
}
static void free_init_section_list ( struct playlist * pls )
{
int i ;
for ( i = 0 ; i < pls -> n_init_sections ; i ++ ) {
av_freep ( & pls -> init_sections [ i ] -> url );
av_freep ( & pls -> init_sections [ i ]);
}
av_freep ( & pls -> init_sections );
pls -> n_init_sections = 0 ;
}
static void free_playlist_list ( HLSContext * c )
{
int i ;
for ( i = 0 ; i < c -> n_playlists_all ; i ++ ) {
struct playlist * pls = c -> playlists_all [ i ];
free_segment_list ( pls );
free_init_section_list ( pls );
av_freep ( & pls -> main_streams );
av_freep ( & pls -> renditions );
av_freep ( & pls -> id3_buf );
av_dict_free ( & pls -> id3_initial );
ff_id3v2_free_extra_meta ( & pls -> id3_deferred_extra );
av_freep ( & pls -> init_sec_buf );
av_packet_unref ( & pls -> pkt );
av_freep ( & pls -> pb . buffer );
if ( pls -> redirect_url )
av_freep ( & pls -> redirect_url );
if ( pls -> timeshif_url )
av_freep ( & pls -> timeshif_url );
if ( pls -> input )
ff_format_io_close ( c -> ctx , & pls -> input );
pls -> input_read_done = 0 ;
if ( pls -> input_next )
ff_format_io_close ( c -> ctx , & pls -> input_next );
if ( pls -> playlist_pb )
ff_format_io_close ( c -> ctx , & pls -> playlist_pb );
pls -> input_next_requested = 0 ;
if ( pls -> ctx ) {
pls -> ctx -> pb = NULL ;
avformat_close_input ( & pls -> ctx );
}
av_free ( pls );
}
av_freep ( & c -> playlists );
av_freep ( & c -> playlists_all );
av_freep ( & c -> cookies );
av_freep ( & c -> user_agent );
av_freep ( & c -> headers );
av_freep ( & c -> http_proxy );
c -> n_playlists = 0 ;
c -> n_playlists_all = 0 ;
}
static void free_variant_list ( HLSContext * c )
{
int i ;
for ( i = 0 ; i < c -> n_variants ; i ++ ) {
struct variant_t * var = c -> variants [ i ];
av_freep ( & var -> playlists );
av_free ( var );
}
#ifdef HLS_I_FRAME_MODE
for ( i = 0 ; i < c -> n_i_frame_variants ; i ++ ) {
struct variant_t * var = c -> i_frame_variants [ i ];
av_freep ( & var -> playlists );
av_free ( var );
}
av_freep ( & c -> i_frame_variants );
c -> n_i_frame_variants = 0 ;
#endif
av_freep ( & c -> variants );
c -> n_variants = 0 ;
}
static void free_rendition_list ( HLSContext * c )
{
int i ;
for ( i = 0 ; i < c -> n_renditions ; i ++ )
av_freep ( & c -> renditions [ i ]);
av_freep ( & c -> renditions );
c -> n_renditions = 0 ;
}
/*
* Used to reset a statically allocated AVPacket to a clean slate,
* containing no data.
*/
static void reset_packet ( AVPacket * pkt )
{
av_init_packet ( pkt );
pkt -> data = NULL ;
}
static struct playlist * new_playlist ( HLSContext * c , const char * url ,
const char * base )
{
struct playlist * pls = av_mallocz ( sizeof ( struct playlist ));
if ( ! pls )
return NULL ;
reset_packet ( & pls -> pkt );
ff_make_absolute_url ( pls -> url , sizeof ( pls -> url ), base , url );
pls -> seek_timestamp = AV_NOPTS_VALUE ;
pls -> is_id3_timestamped = - 1 ;
pls -> id3_mpegts_timestamp = AV_NOPTS_VALUE ;
dynarray_add ( & c -> playlists , & c -> n_playlists , pls );
return pls ;
}
struct variant_info {
char bandwidth [ 20 ];
/* variant group ids: */
char audio [ MAX_FIELD_LEN ];
char video [ MAX_FIELD_LEN ];
char subtitles [ MAX_FIELD_LEN ];
char codecs [ MAX_FIELD_LEN ];
char resolution [ MAX_FIELD_LEN ];
};
static struct variant_t * new_variant ( HLSContext * c , struct variant_info * info ,
const char * url , const char * base )
{
struct variant_t * var ;
struct playlist * pls ;
pls = new_playlist ( c , url , base );
if ( ! pls )
return NULL ;
var = av_mallocz ( sizeof ( struct variant_t ));
if ( ! var )
return NULL ;
if ( info ) {
var -> bandwidth = atoi ( info -> bandwidth );
strcpy ( var -> audio_group , info -> audio );
strcpy ( var -> video_group , info -> video );
strcpy ( var -> subtitles_group , info -> subtitles );
strcpy ( var -> codecs , info -> codecs );
strcpy ( var -> resolution , info -> resolution );
}
dynarray_add ( & c -> variants , & c -> n_variants , var );
dynarray_add ( & var -> playlists , & var -> n_playlists , pls );
return var ;
}
static void handle_variant_args ( struct variant_info * info , const char * key ,
int key_len , char ** dest , int * dest_len )
{
if ( ! strncmp ( key , "BANDWIDTH=" , key_len )) {
* dest = info -> bandwidth ;
* dest_len = sizeof ( info -> bandwidth );
} else if ( ! strncmp ( key , "AUDIO=" , key_len )) {
* dest = info -> audio ;
* dest_len = sizeof ( info -> audio );
} else if ( ! strncmp ( key , "VIDEO=" , key_len )) {
* dest = info -> video ;
* dest_len = sizeof ( info -> video );
} else if ( ! strncmp ( key , "SUBTITLES=" , key_len )) {
* dest = info -> subtitles ;
* dest_len = sizeof ( info -> subtitles );
} else if ( ! strncmp ( key , "CODECS=" , key_len )) {
* dest = info -> codecs ;
* dest_len = sizeof ( info -> codecs );
} else if ( ! strncmp ( key , "RESOLUTION=" , key_len )) {
* dest = info -> resolution ;
* dest_len = sizeof ( info -> resolution );
}
}
typedef struct {
enum AVMediaType type ;
const char * desc ;
} Codec_Desc ;
static Codec_Desc codec_desc [] = {
{ AVMEDIA_TYPE_VIDEO , "avc1" },
{ AVMEDIA_TYPE_VIDEO , "avc2" },
{ AVMEDIA_TYPE_VIDEO , "avc3" },
{ AVMEDIA_TYPE_VIDEO , "avc4" },
{ AVMEDIA_TYPE_AUDIO , "mp4a.40.2" },
{ AVMEDIA_TYPE_AUDIO , "mp4a.40.33" },
{ AVMEDIA_TYPE_AUDIO , "mp4a.40.34" },
{ AVMEDIA_TYPE_AUDIO , "ac-3" },
{ AVMEDIA_TYPE_AUDIO , "ec-3" },
{ AVMEDIA_TYPE_SUBTITLE , "sbtt" },
{ AVMEDIA_TYPE_SUBTITLE , "stpp" },
{ AVMEDIA_TYPE_SUBTITLE , "encu" },
};
static int isCodecContainVideo ( const char * codecs )
{
char * p = ( char * ) codecs ;
char * q ;
char * codec ;
int len ;
int found = 0 ;
int containVideo = 0 ;
while ( p ) {
q = strchr ( p , ',' );
if ( q ) {
len = q - p ;
} else {
len = strlen ( p );
}
codec = av_mallocz ( len + 1 );
if ( ! codec )
return 0 ;
strncpy ( codec , p , len );
for ( int i = 0 ; i < FF_ARRAY_ELEMS ( codec_desc ); i ++ ) {
if ( ! strncmp ( codec , codec_desc [ i ]. desc , 4 )) {
switch ( codec_desc [ i ]. type ) {
case AVMEDIA_TYPE_VIDEO :
containVideo = 1 ;
found = 1 ;
break ;
default :
break ;
}
break ;
}
}
av_free ( codec );
if ( found )
break ;
p = q ? q + 1 : NULL ;
}
return containVideo ;
}
static void handle_key_args ( struct hls_key_info * info , const char * key ,
int key_len , char ** dest , int * dest_len )
{
if ( ! strncmp ( key , "METHOD=" , key_len )) {
* dest = info -> method ;
* dest_len = sizeof ( info -> method );
} else if ( ! strncmp ( key , "URI=" , key_len )) {
* dest = info -> uri ;
* dest_len = sizeof ( info -> uri );
} else if ( ! strncmp ( key , "IV=" , key_len )) {
* dest = info -> iv ;
* dest_len = sizeof ( info -> iv );
} else if ( ! strncmp ( key , "KEYID=" , key_len )) {
* dest = info -> keyid ;
* dest_len = sizeof ( info -> keyid );
}
}
struct init_section_info {
char uri [ MAX_URL_SIZE ];
char byterange [ 32 ];
};
static struct segment * new_init_section ( struct playlist * pls ,
struct init_section_info * info ,
const char * url_base )
{
struct segment * sec ;
char * ptr ;
char tmp_str [ MAX_URL_SIZE ];
if ( ! info -> uri [ 0 ])
return NULL ;
sec = av_mallocz ( sizeof ( * sec ));
if ( ! sec )
return NULL ;
ff_make_absolute_url ( tmp_str , sizeof ( tmp_str ), url_base , info -> uri );
sec -> url = av_strdup ( tmp_str );
if ( ! sec -> url ) {
av_free ( sec );
return NULL ;
}
if ( info -> byterange [ 0 ]) {
sec -> size = strtoll ( info -> byterange , NULL , 10 );
ptr = strchr ( info -> byterange , '@' );
if ( ptr )
sec -> url_offset = strtoll ( ptr + 1 , NULL , 10 );
} else {
/* the entire file is the init section */
sec -> size = - 1 ;
}
dynarray_add ( & pls -> init_sections , & pls -> n_init_sections , sec );
return sec ;
}
static void handle_init_section_args ( struct init_section_info * info , const char * key ,
int key_len , char ** dest , int * dest_len )
{
if ( ! strncmp ( key , "URI=" , key_len )) {
* dest = info -> uri ;
* dest_len = sizeof ( info -> uri );
} else if ( ! strncmp ( key , "BYTERANGE=" , key_len )) {
* dest = info -> byterange ;
* dest_len = sizeof ( info -> byterange );
}
}
struct rendition_info {
char type [ 16 ];
char uri [ MAX_URL_SIZE ];
char group_id [ MAX_FIELD_LEN ];
char language [ MAX_FIELD_LEN ];
char assoc_language [ MAX_FIELD_LEN ];
char name [ MAX_FIELD_LEN ];
char defaultr [ 4 ];
char forced [ 4 ];
char characteristics [ MAX_CHARACTERISTICS_LEN ];
};
static struct rendition * new_rendition ( HLSContext * c , struct rendition_info * info ,
const char * url_base )
{
struct rendition * rend ;
enum AVMediaType type = AVMEDIA_TYPE_UNKNOWN ;
char * characteristic ;
char * chr_ptr ;
char * saveptr ;
if ( ! strcmp ( info -> type , "AUDIO" ))
type = AVMEDIA_TYPE_AUDIO ;
else if ( ! strcmp ( info -> type , "VIDEO" ))
type = AVMEDIA_TYPE_VIDEO ;
else if ( ! strcmp ( info -> type , "SUBTITLES" ))
type = AVMEDIA_TYPE_SUBTITLE ;
else if ( ! strcmp ( info -> type , "CLOSED-CAPTIONS" ))
/* CLOSED-CAPTIONS is ignored since we do not support CEA-608 CC in
* AVC SEI RBSP anyway */
return NULL ;
if ( type == AVMEDIA_TYPE_UNKNOWN )
return NULL ;
/* URI is mandatory for subtitles as per spec */
if ( type == AVMEDIA_TYPE_SUBTITLE && ! info -> uri [ 0 ])
return NULL ;
/* TODO: handle subtitles (each segment has to parsed separately) */
// if (c->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL)
// if (type == AVMEDIA_TYPE_SUBTITLE)
// return NULL;
rend = av_mallocz ( sizeof ( struct rendition ));
if ( ! rend )
return NULL ;
dynarray_add ( & c -> renditions , & c -> n_renditions , rend );
rend -> type = type ;
strcpy ( rend -> group_id , info -> group_id );
strcpy ( rend -> language , info -> language );
strcpy ( rend -> name , info -> name );
/* add the playlist if this is an external rendition */
if ( info -> uri [ 0 ]) {
rend -> playlist = new_playlist ( c , info -> uri , url_base );
if ( rend -> playlist )
dynarray_add ( & rend -> playlist -> renditions ,
& rend -> playlist -> n_renditions , rend );
}
if ( info -> assoc_language [ 0 ]) {
int langlen = strlen ( rend -> language );
if ( langlen < sizeof ( rend -> language ) - 3 ) {
rend -> language [ langlen ] = ',' ;
strncpy ( rend -> language + langlen + 1 , info -> assoc_language ,
sizeof ( rend -> language ) - langlen - 2 );
}
}
if ( ! strcmp ( info -> defaultr , "YES" ))
rend -> disposition |= AV_DISPOSITION_DEFAULT ;
if ( ! strcmp ( info -> forced , "YES" ))
rend -> disposition |= AV_DISPOSITION_FORCED ;
chr_ptr = info -> characteristics ;
while (( characteristic = av_strtok ( chr_ptr , "," , & saveptr ))) {
if ( ! strcmp ( characteristic , "public.accessibility.describes-music-and-sound" ))
rend -> disposition |= AV_DISPOSITION_HEARING_IMPAIRED ;
else if ( ! strcmp ( characteristic , "public.accessibility.describes-video" ))
rend -> disposition |= AV_DISPOSITION_VISUAL_IMPAIRED ;
chr_ptr = NULL ;
}
return rend ;
}
static void handle_rendition_args ( struct rendition_info * info , const char * key ,
int key_len , char ** dest , int * dest_len )
{
if ( ! strncmp ( key , "TYPE=" , key_len )) {
* dest = info -> type ;
* dest_len = sizeof ( info -> type );
} else if ( ! strncmp ( key , "URI=" , key_len )) {
* dest = info -> uri ;
* dest_len = sizeof ( info -> uri );
} else if ( ! strncmp ( key , "GROUP-ID=" , key_len )) {
* dest = info -> group_id ;
* dest_len = sizeof ( info -> group_id );
} else if ( ! strncmp ( key , "LANGUAGE=" , key_len )) {
* dest = info -> language ;
* dest_len = sizeof ( info -> language );
} else if ( ! strncmp ( key , "ASSOC-LANGUAGE=" , key_len )) {
* dest = info -> assoc_language ;
* dest_len = sizeof ( info -> assoc_language );
} else if ( ! strncmp ( key , "NAME=" , key_len )) {
* dest = info -> name ;
* dest_len = sizeof ( info -> name );
} else if ( ! strncmp ( key , "DEFAULT=" , key_len )) {
* dest = info -> defaultr ;
* dest_len = sizeof ( info -> defaultr );
} else if ( ! strncmp ( key , "FORCED=" , key_len )) {
* dest = info -> forced ;
* dest_len = sizeof ( info -> forced );
} else if ( ! strncmp ( key , "CHARACTERISTICS=" , key_len )) {
* dest = info -> characteristics ;
* dest_len = sizeof ( info -> characteristics );
}
/*
* ignored:
* - AUTOSELECT: client may autoselect based on e.g. system language
* - INSTREAM-ID: EIA-608 closed caption number ("CC1".."CC4")
*/
}
/* used by parse_playlist to allocate a new variant+playlist when the
* playlist is detected to be a Media Playlist (not Master Playlist)
* and we have no parent Master Playlist (parsing of which would have
* allocated the variant and playlist already)
* *pls == NULL => Master Playlist or parentless Media Playlist
* *pls != NULL => parented Media Playlist, playlist+variant allocated */
static int ensure_playlist ( HLSContext * c , struct playlist ** pls , const char * url )
{
if ( * pls )
return 0 ;
if ( ! new_variant ( c , NULL , url , NULL ))
return AVERROR ( ENOMEM );
* pls = c -> playlists [ c -> n_playlists - 1 ];
return 0 ;
}
static void update_options ( char ** dest , const char * name , void * src )
{
av_freep ( dest );
av_opt_get ( src , name , AV_OPT_SEARCH_CHILDREN , ( uint8_t ** ) dest );
if ( * dest && ! strlen ( * dest ))
av_freep ( dest );
}
static int open_url_keepalive ( AVFormatContext * s , AVIOContext ** pb ,
const char * url )
{
//add for amlogic, not support keepalive for not suitable with our framework
// #if !CONFIG_HTTP_PROTOCOL
return AVERROR_PROTOCOL_NOT_FOUND ;
// #else
// int ret;
// URLContext *uc = ffio_geturlcontext(*pb);
// av_assert0(uc);
// (*pb)->eof_reached = 0;
// ret = aml_http_do_new_request(uc, url);
// if (ret < 0) {
// ff_format_io_close(s, pb);
// }
// return ret;
// #endif
}
static int open_url ( AVFormatContext * s , AVIOContext ** pb , const char * url ,
AVDictionary * opts , AVDictionary * opts2 , int * is_http_out )
{
HLSContext * c = s -> priv_data ;
AVDictionary * tmp = NULL ;
const char * proto_name = NULL ;
int ret ;
int is_http = 0 ;
av_dict_copy ( & tmp , opts , 0 );
av_dict_copy ( & tmp , opts2 , 0 );
if ( av_strstart ( url , "crypto" , NULL )) {
if ( url [ 6 ] == '+' || url [ 6 ] == ':' )
proto_name = avio_find_protocol_name ( url + 7 );
}
if ( ! proto_name )
proto_name = avio_find_protocol_name ( url );
if ( ! proto_name )
return AVERROR_INVALIDDATA ;
// only http(s) & file are allowed
if ( av_strstart ( proto_name , "file" , NULL )) {
if ( strcmp ( c -> allowed_extensions , "ALL" ) && ! av_match_ext ( url , c -> allowed_extensions )) {
av_log ( NULL , AV_LOG_ERROR , "Filename extension of \' %s \' is not a common multimedia extension, blocked for security reasons. \n "
"If you wish to override this adjust allowed_extensions, you can set it to \' ALL \' to allow all \n " ,
url );
return AVERROR_INVALIDDATA ;
}
} else if ( av_strstart ( proto_name , "http" , NULL )) {
is_http = 1 ;
} else
return AVERROR_INVALIDDATA ;
if ( ! strncmp ( proto_name , url , strlen ( proto_name )) && url [ strlen ( proto_name )] == ':' )
;
else if ( av_strstart ( url , "crypto" , NULL ) && ! strncmp ( proto_name , url + 7 , strlen ( proto_name )) && url [ 7 + strlen ( proto_name )] == ':' )
;
else if ( strcmp ( proto_name , "file" ) || ! strncmp ( url , "file," , 5 ))
return AVERROR_INVALIDDATA ;
if ( is_http && c -> http_persistent && * pb ) {
ret = open_url_keepalive ( c -> ctx , pb , url );
if ( ret == AVERROR_EXIT ) {
return ret ;
} else if ( ret < 0 ) {
if ( ret != AVERROR_EOF )
av_log ( NULL , AV_LOG_WARNING , "keepalive request failed for '%s', retrying with new connection: %s \n " ,
url , av_err2str ( ret ));
//ret = s->io_open(s, pb, url, AVIO_FLAG_READ, &tmp);
ret = amlio_open_whitelist ( pb , url , AVIO_FLAG_READ , & s -> interrupt_callback , & tmp ,
s -> protocol_whitelist , s -> protocol_blacklist );
}
} else {
//ret = s->io_open(s, pb, url, AVIO_FLAG_READ, &tmp);
ret = amlio_open_whitelist ( pb , url , AVIO_FLAG_READ , & s -> interrupt_callback , & tmp ,
s -> protocol_whitelist , s -> protocol_blacklist );
}
if ( ret >= 0 ) {
// update cookies on http response with setcookies.
char * new_cookies = NULL ;
if ( ! ( s -> flags & AVFMT_FLAG_CUSTOM_IO ))
av_opt_get ( * pb , "cookies" , AV_OPT_SEARCH_CHILDREN , ( uint8_t ** ) & new_cookies );
if ( new_cookies ) {
av_free ( c -> cookies );
c -> cookies = new_cookies ;
}
av_dict_set ( & opts , "cookies" , c -> cookies , 0 );
if ( c -> http_thread_enable )
{
//will use the buf in http proto
//to avoid copy ts data one more time
//so close the buf in this AVIOContext
( * pb ) -> direct = 1 ;
}
}
av_dict_free ( & tmp );
if ( is_http_out )
* is_http_out = is_http ;
return ret ;
}
static void get_m3u8_opts ( HLSContext * c , AVDictionary ** _opts )
{
int ret = 0 ;
/* Some HLS servers don't like being sent the range header */
// ret = av_dict_set(_opts, "seekable", "0", 0);
// broker prior HTTP options that should be consistent across requests
ret = av_dict_set ( _opts , "user_agent" , c -> user_agent , 0 );
ret = av_dict_set ( _opts , "cookies" , c -> cookies , 0 );
ret = av_dict_set ( _opts , "headers" , c -> headers , 0 );
ret = av_dict_set ( _opts , "http_proxy" , c -> http_proxy , 0 );
//opt for m3u8 http
ret = av_dict_set_int ( _opts , "seekable" , c -> seekable , 0 );
ret = av_dict_set_int ( _opts , "http_reconnect" , 0 , 0 );
ret = av_dict_set_int ( _opts , "http_reconnect_at_eof" , c -> http_reconnect_at_eof , 0 );
ret = av_dict_set_int ( _opts , "http_reconnect_streamed" , c -> http_reconnect_streamed , 0 );
ret = av_dict_set_int ( _opts , "http_reconnect_delay_max" , c -> http_reconnect_delay_max , 0 );
ret = av_dict_set_int ( _opts , "http_thread_enable" , c -> http_thread_enable , 0 );
ret = av_dict_set_int ( _opts , "http_thread_bufsize" , c -> http_thread_bufsize , 0 );
ret = av_dict_set_int ( _opts , "tcp_rw_timeout" , c -> tcp_rw_timeout , 0 );
ret = av_dict_set_int ( _opts , "tcp_connect_timeout" , c -> tcp_connect_timeout , 0 );
ret = av_dict_set_int ( _opts , "addrinfo_one_by_one" , c -> addrinfo_one_by_one , 0 );
ret = av_dict_set_int ( _opts , "dns_seq_mode" , c -> dns_seq_mode , 0 );
ret = av_dict_set_int ( _opts , "addrinfo_timeout" , c -> addrinfo_timeout , 0 );
ret = av_dict_set_int ( _opts , "dns_cache_timeout" , c -> dns_cache_timeout , 0 );
if ( c -> http_persistent )
ret = av_dict_set ( _opts , "multiple_requests" , "1" , 0 );
}
int parse_playlist_start_seq ( AVIOContext * in )
{
int ret = - 1 ;
const char * ptr ;
if ( in == NULL )
{
av_log ( NULL , AV_LOG_ERROR , "in == NULL" );
return - 1 ;
}
char line [ MAX_URL_SIZE ];
ff_get_chomp_line ( in , line , sizeof ( line ));
if ( strcmp ( line , "#EXTM3U" )) {
ret = AVERROR_INVALIDDATA ;
goto fail ;
}
while ( ! avio_feof ( in )) {
ff_get_chomp_line ( in , line , sizeof ( line ));
if ( av_strstart ( line , "#EXT-X-MEDIA-SEQUENCE:" , & ptr )) {
ret = atoi ( ptr );
break ;
}
}
fail :
avio_seek ( in , 0 , SEEK_SET );
return ret ;
}
static int64_t parse_date_time_us ( const char * timestamp )
{
struct tm tm = { 0 };
char timestamp_strp [ 50 ];
char timestamp_ms [ 4 ];
memset ( timestamp_strp , 0 , sizeof ( timestamp_strp ));
memset ( timestamp_ms , 0 , sizeof ( timestamp_ms ));
char * ms_str = strstr ( timestamp , "." );
if ( ms_str ) {
memcpy ( timestamp_strp , timestamp , ms_str - timestamp );
memcpy ( timestamp_strp + ( ms_str - timestamp ), ms_str + 4 , strlen ( ms_str + 4 ));
memcpy ( timestamp_ms , ms_str + 1 , 3 );
} else {
memcpy ( timestamp_strp , timestamp , strlen ( timestamp ));
}
strptime ( timestamp_strp , "%Y-%m-%dT%H:%M:%S%z" , & tm );
char * tz = getenv ( "TZ" );
int64_t unixTimestamp = mktime ( & tm );
int ms = atoi ( timestamp_ms );
int64_t us = ( unixTimestamp - timezone ) * 1000000 + ms * 1000 ;
av_log ( NULL , AV_LOG_WARNING , "timestamp_strp:%s, timestamp_ms:%s, us:%lld, unixTimestamp:%lld, timezone:%ld, tz:%s \n " , timestamp_strp , timestamp_ms , us , unixTimestamp , timezone , tz ? tz : "tz=NULL" );
return us ;
}
static int parse_playlist ( HLSContext * c , const char * url ,
struct playlist * pls , AVIOContext * in )
{
int ret = 0 , is_segment = 0 , is_variant = 0 ;
int64_t duration = 0 ;
int64_t seg_time_us = - 1 ;
enum KeyType key_type = KEY_NONE ;
uint8_t iv [ 16 ] = "" ;
int has_iv = 0 ;
char key [ MAX_URL_SIZE ] = "" ;
char line [ MAX_URL_SIZE ];
const char * ptr ;
int close_in = 0 ;
int64_t seg_offset = 0 ;
int64_t seg_size = - 1 ;
uint8_t * new_url = NULL ;
struct variant_info variant_info ;
#ifdef HLS_I_FRAME_MODE
struct i_frame_variant_info i_frame_variant_info ;
#endif
char tmp_str [ MAX_URL_SIZE ];
struct segment * cur_init_section = NULL ;
char * url_origin = url ;
int is_http = av_strstart ( url , "http" , NULL );
if ( is_http && ! in && c -> http_persistent && c -> playlist_pb && ! c -> hls_thread_enable ) {
in = c -> playlist_pb ;
ret = open_url_keepalive ( c -> ctx , & c -> playlist_pb , url );
if ( ret == AVERROR_EXIT ) {
return ret ;
} else if ( ret < 0 ) {
if ( ret != AVERROR_EOF )
av_log ( NULL , AV_LOG_WARNING , "keepalive request failed for '%s', retrying with new connection: %s \n " ,
url , av_err2str ( ret ));
in = NULL ;
}
}
if ( ! in ) {
AVDictionary * opts = NULL ;
get_m3u8_opts ( c , & opts );
//ret = c->ctx->io_open(c->ctx, &in, url, AVIO_FLAG_READ, &opts);
ret = amlio_open_whitelist ( & in , url , AVIO_FLAG_READ , & c -> ctx -> interrupt_callback , & opts ,
c -> ctx -> protocol_whitelist , c -> ctx -> protocol_blacklist );
av_dict_free ( & opts );
if ( ret < 0 )
{
if ( check_net_phy_conn_status () == 0 )
{
av_log ( NULL , AV_LOG_WARNING , "hls net down" );
set_dns_net_down ();
return AVERROR ( ETIMEDOUT );
}
if ( pls && pls -> redirect_url )
av_freep ( & pls -> redirect_url );
return ret ;
}
if ( is_http && c -> http_persistent )
c -> playlist_pb = in ;
else
close_in = 1 ;
}
if ( av_opt_get ( in , "location" , AV_OPT_SEARCH_CHILDREN , & new_url ) >= 0 && new_url )
{
//sometimes new_url="", but don't know why yet. add a protection
if ( strcmp ( new_url , "" )) {
url = new_url ;
if ( pls )
{
if ( pls -> redirect_url )
av_freep ( & pls -> redirect_url );
pls -> redirect_url = av_strdup ( new_url );
}
}
av_log ( NULL , AV_LOG_INFO , "location get new_url:%s, url:%s, redirect_url:%s \n " , trimLeadingCharacter ( new_url ), trimLeadingCharacter ( url ), ( pls ? trimLeadingCharacter ( pls -> redirect_url ) : NULL ));
} else {
av_log ( NULL , AV_LOG_INFO , "location get new_url failed! \n " );
}
if ( c -> print_m3u8 )
{
while ( ! avio_feof ( in )) {
ff_get_chomp_line ( in , line , sizeof ( line ));
av_log ( NULL , AV_LOG_VERBOSE , "m3u8 line:%s" , line );
}
avio_seek ( in , 0 , SEEK_SET );
}
ff_get_chomp_line ( in , line , sizeof ( line ));
if ( strcmp ( line , "#EXTM3U" )) {
ret = AVERROR_INVALIDDATA ;
goto fail ;
}
if ( pls ) {
free_segment_list ( pls );
pls -> finished = 0 ;
pls -> type = PLS_TYPE_UNSPECIFIED ;
}
while ( ! avio_feof ( in )) {
if (( ret = ff_get_chomp_line ( in , line , sizeof ( line ))) < 0 ) {
av_log ( NULL , AV_LOG_ERROR , "ff_get_chomp_line return error:%d" , ret );
break ;
}
if ( av_strstart ( line , "#EXT-X-STREAM-INF:" , & ptr )) {
is_variant = 1 ;
memset ( & variant_info , 0 , sizeof ( variant_info ));
ff_parse_key_value ( ptr , ( ff_parse_key_val_cb ) handle_variant_args ,
& variant_info );
}
#ifdef HLS_I_FRAME_MODE
else if ( av_strstart ( line , "#EXT-X-I-FRAME-STREAM-INF:" , & ptr )) {
av_log ( NULL , AV_LOG_VERBOSE , "enter #EXT-X-I-FRAME-STREAM-IN------------>" );
memset ( & variant_info , 0 , sizeof ( variant_info ));
ff_parse_key_value ( ptr , ( ff_parse_key_val_cb ) handle_i_frame_variant_args ,
& i_frame_variant_info );
if ( ! new_i_frame_variant ( c , & i_frame_variant_info , url )) {
// av_log(NULL, AV_LOG_VERBOSE,"new_i_frame_variant11------------>line:%s, url:%s",line, url);
ret = AVERROR ( ENOMEM );
goto fail ;
} else {
//av_log(NULL, AV_LOG_VERBOSE,"new_i_frame_variant22------------>line:%s, url:%s",line, url);
}
}
#endif
else if ( av_strstart ( line , "#EXT-X-KEY:" , & ptr )) {
struct hls_key_info info = {{ 0 }};
ff_parse_key_value ( ptr , ( ff_parse_key_val_cb ) handle_key_args ,
& info );
key_type = KEY_NONE ;
has_iv = 0 ;
if ( ! strcmp ( info . method , "AES-128" ))
key_type = KEY_AES_128 ;
if ( ! strcmp ( info . method , "SAMPLE-AES-CTR" ))
key_type = KEY_SAMPLE_AES_CTR ;
else if ( ! strcmp ( info . method , "SAMPLE-AES" ))
key_type = KEY_SAMPLE_AES ;
if ( ! strncmp ( info . iv , "0x" , 2 ) || ! strncmp ( info . iv , "0X" , 2 )) {
ff_hex_to_data ( iv , info . iv + 2 );
has_iv = 1 ;
}
av_strlcpy ( key , info . uri , sizeof ( key ));
// todo, how to report to AmIptvMedia?
// base64 to binary
const char * base64Prefix = "data:text/plain;base64," ;
if (( key_type == KEY_SAMPLE_AES || key_type == KEY_SAMPLE_AES_CTR )
&& info . uri && strlen ( info . uri ) > strlen ( base64Prefix )
&& ! strncmp ( info . uri , base64Prefix , strlen ( base64Prefix ))) {
char * base64Str = info . uri + strlen ( base64Prefix );
size_t size = AV_BASE64_DECODE_SIZE ( strlen ( base64Str ));
uint8_t * pssh = av_malloc ( size );
int pssh_size = av_base64_decode ( pssh , base64Str , size );
av_log ( 0 , AV_LOG_ERROR , "notify base64Str:%s pssh: %s" , base64Str , ( char * ) pssh );
2026-08-25 10:02:36 +08:00
c -> hls_notify_direct (( void * ) c -> session , ffObFFMPEG_PSSH_UPDATE , ( int64_t )( intptr_t ) pssh , pssh_size , 0 );
2026-08-13 11:28:40 +08:00
av_free ( pssh );
}
} else if ( av_strstart ( line , "#EXT-X-MEDIA:" , & ptr )) {
struct rendition_info info = {{ 0 }};
ff_parse_key_value ( ptr , ( ff_parse_key_val_cb ) handle_rendition_args ,
& info );
new_rendition ( c , & info , url_origin );
} else if ( av_strstart ( line , "#EXT-X-TARGETDURATION:" , & ptr )) {
ret = ensure_playlist ( c , & pls , url_origin );
if ( ret < 0 )
goto fail ;
pls -> target_duration = strtoll ( ptr , NULL , 10 ) * AV_TIME_BASE ;
} else if ( av_strstart ( line , "#EXT-X-MEDIA-SEQUENCE:" , & ptr )) {
ret = ensure_playlist ( c , & pls , url_origin );
if ( ret < 0 )
goto fail ;
pls -> start_seq_no = atoi ( ptr );
} else if ( av_strstart ( line , "#EXT-X-PROGRAM-DATE-TIME:" , & ptr )) {
seg_time_us = parse_date_time_us ( ptr );
av_log ( 0 , AV_LOG_VERBOSE , "Program date time: %lld \n " , seg_time_us );
} else if ( av_strstart ( line , "#EXT-X-PLAYLIST-TYPE:" , & ptr )) {
ret = ensure_playlist ( c , & pls , url_origin );
if ( ret < 0 )
goto fail ;
if ( ! strcmp ( ptr , "EVENT" ))
pls -> type = PLS_TYPE_EVENT ;
else if ( ! strcmp ( ptr , "VOD" ))
pls -> type = PLS_TYPE_VOD ;
} else if ( av_strstart ( line , "#EXT-X-MAP:" , & ptr )) {
struct init_section_info info = {{ 0 }};
ret = ensure_playlist ( c , & pls , url_origin );
if ( ret < 0 )
goto fail ;
ff_parse_key_value ( ptr , ( ff_parse_key_val_cb ) handle_init_section_args ,
& info );
cur_init_section = new_init_section ( pls , & info , url );
} else if ( av_strstart ( line , "#EXT-X-ENDLIST" , & ptr )) {
if ( pls )
pls -> finished = 1 ;
} else if ( av_strstart ( line , "#EXTINF:" , & ptr )) {
is_segment = 1 ;
duration = atof ( ptr ) * AV_TIME_BASE ;
} else if ( av_strstart ( line , "#EXT-X-BYTERANGE:" , & ptr )) {
seg_size = strtoll ( ptr , NULL , 10 );
ptr = strchr ( ptr , '@' );
if ( ptr )
seg_offset = strtoll ( ptr + 1 , NULL , 10 );
} else if ( av_strstart ( line , "#EXT-X-DISCONTINUITY" , & ptr )) {
av_log ( NULL , AV_LOG_WARNING , "#EXT-X-DISCONTINUITY found!" );
} else if ( av_strstart ( line , "#" , NULL )) {
continue ;
} else if ( line [ 0 ]) {
if ( is_variant ) {
if ( ! new_variant ( c , & variant_info , line , url_origin )) {
ret = AVERROR ( ENOMEM );
goto fail ;
}
is_variant = 0 ;
}
if ( is_segment ) {
struct segment * seg ;
if ( ! pls ) {
if ( ! new_variant ( c , 0 , url_origin , NULL )) {
ret = AVERROR ( ENOMEM );
goto fail ;
}
pls = c -> playlists [ c -> n_playlists - 1 ];
}
seg = av_malloc ( sizeof ( struct segment ));
if ( ! seg ) {
ret = AVERROR ( ENOMEM );
goto fail ;
}
seg -> fetch_starttime = 0 ;
seg -> last_readtime = 0 ;
seg -> duration = duration ;
seg -> timestamp_us_in_url = seg -> timestamp_us_in_url_next = seg_time_us ;
seg_time_us += duration ;
seg -> key_type = key_type ;
c -> ctx_key_type = key_type ;
if ( has_iv ) {
memcpy ( seg -> iv , iv , sizeof ( iv ));
} else {
int seq = pls -> start_seq_no + pls -> n_segments ;
memset ( seg -> iv , 0 , sizeof ( seg -> iv ));
AV_WB32 ( seg -> iv + 12 , seq );
}
if ( key_type != KEY_NONE ) {
ff_make_absolute_url ( tmp_str , sizeof ( tmp_str ), url , key );
seg -> key = av_strdup ( tmp_str );
if ( ! seg -> key ) {
av_free ( seg );
ret = AVERROR ( ENOMEM );
goto fail ;
}
} else {
seg -> key = NULL ;
}
ff_make_absolute_url ( tmp_str , sizeof ( tmp_str ), url , line );
seg -> url = av_strdup ( tmp_str );
if ( c -> print_m3u8 )
{
av_log ( NULL , AV_LOG_VERBOSE , "seg url:%s" , trimLeadingCharacter ( seg -> url ));
av_log ( NULL , AV_LOG_VERBOSE , "url url:%s" , trimLeadingCharacter ( url ));
av_log ( NULL , AV_LOG_VERBOSE , "line:%s" , line );
}
if ( ! seg -> url ) {
av_free ( seg -> key );
av_free ( seg );
ret = AVERROR ( ENOMEM );
goto fail ;
}
dynarray_add ( & pls -> segments , & pls -> n_segments , seg );
is_segment = 0 ;
seg -> size = seg_size ;
if ( seg_size >= 0 ) {
seg -> url_offset = seg_offset ;
seg_offset += seg_size ;
seg_size = - 1 ;
} else {
seg -> url_offset = 0 ;
seg_offset = 0 ;
}
seg -> init_section = cur_init_section ;
}
}
}
if ( pls )
pls -> last_load_time = av_gettime_relative ();
fail :
av_free ( new_url );
if ( close_in )
ff_format_io_close ( c -> ctx , & in );
c -> ctx -> ctx_flags = c -> ctx -> ctx_flags & ~ ( unsigned ) AVFMTCTX_UNSEEKABLE ;
if ( ! c -> n_variants || ! c -> variants [ c -> varrant_used ] -> n_playlists ||
! ( c -> variants [ c -> varrant_used ] -> playlists [ 0 ] -> finished ||
c -> variants [ c -> varrant_used ] -> playlists [ 0 ] -> type == PLS_TYPE_EVENT ))
c -> ctx -> ctx_flags |= AVFMTCTX_UNSEEKABLE ;
return ret ;
}
static struct segment * current_segment ( struct playlist * pls )
{
return pls -> segments [ pls -> cur_seq_no - pls -> start_seq_no ];
}
static struct segment * next_segment ( struct playlist * pls )
{
int n = pls -> cur_seq_no - pls -> start_seq_no + 1 ;
if ( n >= pls -> n_segments )
return NULL ;
return pls -> segments [ n ];
}
enum ReadFromURLMode {
READ_NORMAL ,
READ_COMPLETE ,
};
static int read_from_url ( struct playlist * pls , struct segment * seg ,
uint8_t * buf , int buf_size ,
enum ReadFromURLMode mode )
{
int ret ;
/* limit read if the segment was only a part of a file */
if ( seg -> size >= 0 )
buf_size = FFMIN ( buf_size , seg -> size - pls -> cur_seg_offset );
if ( buf_size <= 0 )
{
return AVERROR_EOF ;
}
if ( mode == READ_COMPLETE ) {
ret = avio_read ( pls -> input , buf , buf_size );
if ( ret != buf_size )
av_log ( NULL , AV_LOG_ERROR , "Could not read complete segment. \n " );
} else
ret = avio_read ( pls -> input , buf , buf_size );
if ( ret > 0 )
pls -> cur_seg_offset += ret ;
return ret ;
}
/* Parse the raw ID3 data and pass contents to caller */
static void parse_id3 ( AVFormatContext * s , AVIOContext * pb ,
AVDictionary ** metadata , int64_t * dts ,
ID3v2ExtraMetaAPIC ** apic , ID3v2ExtraMeta ** extra_meta )
{
static const char id3_priv_owner_ts [] = "com.apple.streaming.transportStreamTimestamp" ;
ID3v2ExtraMeta * meta ;
ff_id3v2_read_dict ( pb , metadata , ID3v2_DEFAULT_MAGIC , extra_meta );
for ( meta = * extra_meta ; meta ; meta = meta -> next ) {
if ( ! strcmp ( meta -> tag , "PRIV" )) {
ID3v2ExtraMetaPRIV * priv = meta -> data ;
if ( priv -> datasize == 8 && ! strcmp ( priv -> owner , id3_priv_owner_ts )) {
/* 33-bit MPEG timestamp */
int64_t ts = AV_RB64 ( priv -> data );
av_log ( NULL , AV_LOG_DEBUG , "HLS ID3 audio timestamp %" PRId64 " \n " , ts );
if (( ts & ~ (( 1ULL << 33 ) - 1 )) == 0 )
* dts = ts ;
else
av_log ( NULL , AV_LOG_ERROR , "Invalid HLS ID3 audio timestamp %" PRId64 " \n " , ts );
}
} else if ( ! strcmp ( meta -> tag , "APIC" ) && apic )
* apic = meta -> data ;
}
}
/* Check if the ID3 metadata contents have changed */
static int id3_has_changed_values ( struct playlist * pls , AVDictionary * metadata ,
ID3v2ExtraMetaAPIC * apic )
{
AVDictionaryEntry * entry = NULL ;
AVDictionaryEntry * oldentry ;
/* check that no keys have changed values */
while (( entry = av_dict_get ( metadata , "" , entry , AV_DICT_IGNORE_SUFFIX ))) {
oldentry = av_dict_get ( pls -> id3_initial , entry -> key , NULL , AV_DICT_MATCH_CASE );
if ( ! oldentry || strcmp ( oldentry -> value , entry -> value ) != 0 )
return 1 ;
}
/* check if apic appeared */
if ( apic && ( pls -> ctx -> nb_streams != 2 || ! pls -> ctx -> streams [ 1 ] -> attached_pic . data ))
return 1 ;
if ( apic ) {
int size = pls -> ctx -> streams [ 1 ] -> attached_pic . size ;
if ( size != apic -> buf -> size - AV_INPUT_BUFFER_PADDING_SIZE )
return 1 ;
if ( memcmp ( apic -> buf -> data , pls -> ctx -> streams [ 1 ] -> attached_pic . data , size ) != 0 )
return 1 ;
}
return 0 ;
}
/* Parse ID3 data and handle the found data */
static void handle_id3 ( AVIOContext * pb , struct playlist * pls )
{
AVDictionary * metadata = NULL ;
ID3v2ExtraMetaAPIC * apic = NULL ;
ID3v2ExtraMeta * extra_meta = NULL ;
int64_t timestamp = AV_NOPTS_VALUE ;
parse_id3 ( pls -> ctx , pb , & metadata , & timestamp , & apic , & extra_meta );
if ( timestamp != AV_NOPTS_VALUE ) {
pls -> id3_mpegts_timestamp = timestamp ;
pls -> id3_offset = 0 ;
}
if ( ! pls -> id3_found ) {
/* initial ID3 tags */
av_assert0 ( ! pls -> id3_deferred_extra );
pls -> id3_found = 1 ;
/* get picture attachment and set text metadata */
if ( pls -> ctx -> nb_streams )
ff_id3v2_parse_apic ( pls -> ctx , & extra_meta );
else
/* demuxer not yet opened, defer picture attachment */
pls -> id3_deferred_extra = extra_meta ;
ff_id3v2_parse_priv_dict ( & metadata , & extra_meta );
av_dict_copy ( & pls -> ctx -> metadata , metadata , 0 );
pls -> id3_initial = metadata ;
} else {
if ( ! pls -> id3_changed && id3_has_changed_values ( pls , metadata , apic )) {
avpriv_report_missing_feature ( pls -> ctx , "Changing ID3 metadata in HLS audio elementary stream" );
pls -> id3_changed = 1 ;
}
av_dict_free ( & metadata );
}
if ( ! pls -> id3_deferred_extra )
ff_id3v2_free_extra_meta ( & extra_meta );
}
static void intercept_id3 ( struct playlist * pls , uint8_t * buf ,
int buf_size , int * len )
{
/* intercept id3 tags, we do not want to pass them to the raw
* demuxer on all segment switches */
int bytes ;
int id3_buf_pos = 0 ;
int fill_buf = 0 ;
struct segment * seg = current_segment ( pls );
/* gather all the id3 tags */
while ( 1 ) {
/* see if we can retrieve enough data for ID3 header */
if ( * len < ID3v2_HEADER_SIZE && buf_size >= ID3v2_HEADER_SIZE ) {
bytes = read_from_url ( pls , seg , buf + * len , ID3v2_HEADER_SIZE - * len , READ_COMPLETE );
if ( bytes > 0 ) {
if ( bytes == ID3v2_HEADER_SIZE - * len )
/* no EOF yet, so fill the caller buffer again after
* we have stripped the ID3 tags */
fill_buf = 1 ;
* len += bytes ;
} else if ( * len <= 0 ) {
/* error/EOF */
* len = bytes ;
fill_buf = 0 ;
}
}
if ( * len < ID3v2_HEADER_SIZE )
break ;
if ( ff_id3v2_match ( buf , ID3v2_DEFAULT_MAGIC )) {
int64_t maxsize = seg -> size >= 0 ? seg -> size : 1024 * 1024 ;
int taglen = ff_id3v2_tag_len ( buf );
int tag_got_bytes = FFMIN ( taglen , * len );
int remaining = taglen - tag_got_bytes ;
if ( taglen > maxsize ) {
av_log ( NULL , AV_LOG_ERROR , "Too large HLS ID3 tag (%d > %" PRId64 " bytes) \n " ,
taglen , maxsize );
break ;
}
/*
* Copy the id3 tag to our temporary id3 buffer.
* We could read a small id3 tag directly without memcpy, but
* we would still need to copy the large tags, and handling
* both of those cases together with the possibility for multiple
* tags would make the handling a bit complex.
*/
pls -> id3_buf = av_fast_realloc ( pls -> id3_buf , & pls -> id3_buf_size , id3_buf_pos + taglen );
if ( ! pls -> id3_buf )
break ;
memcpy ( pls -> id3_buf + id3_buf_pos , buf , tag_got_bytes );
id3_buf_pos += tag_got_bytes ;
/* strip the intercepted bytes */
* len -= tag_got_bytes ;
memmove ( buf , buf + tag_got_bytes , * len );
av_log ( NULL , AV_LOG_DEBUG , "Stripped %d HLS ID3 bytes \n " , tag_got_bytes );
if ( remaining > 0 ) {
/* read the rest of the tag in */
if ( read_from_url ( pls , seg , pls -> id3_buf + id3_buf_pos , remaining , READ_COMPLETE ) != remaining )
break ;
id3_buf_pos += remaining ;
av_log ( NULL , AV_LOG_DEBUG , "Stripped additional %d HLS ID3 bytes \n " , remaining );
}
} else {
/* no more ID3 tags */
break ;
}
}
/* re-fill buffer for the caller unless EOF */
if ( * len >= 0 && ( fill_buf || * len == 0 )) {
bytes = read_from_url ( pls , seg , buf + * len , buf_size - * len , READ_NORMAL );
/* ignore error if we already had some data */
if ( bytes >= 0 )
* len += bytes ;
else if ( * len == 0 )
* len = bytes ;
}
if ( pls -> id3_buf ) {
/* Now parse all the ID3 tags */
AVIOContext id3ioctx ;
ffio_init_context ( & id3ioctx , pls -> id3_buf , id3_buf_pos , 0 , NULL , NULL , NULL , NULL );
handle_id3 ( & id3ioctx , pls );
}
if ( pls -> is_id3_timestamped == - 1 )
pls -> is_id3_timestamped = ( pls -> id3_mpegts_timestamp != AV_NOPTS_VALUE );
}
static int open_input ( HLSContext * c , struct playlist * pls , struct segment * seg , AVIOContext ** in )
{
AVDictionary * opts = NULL ;
int ret ;
int is_http = 0 ;
// broker prior HTTP options that should be consistent across requests
av_dict_set ( & opts , "user_agent" , c -> user_agent , 0 );
av_dict_set ( & opts , "referer" , c -> referer , 0 );
av_dict_set ( & opts , "cookies" , c -> cookies , 0 );
av_dict_set ( & opts , "headers" , c -> headers , 0 );
av_dict_set ( & opts , "http_proxy" , c -> http_proxy , 0 );
if ( c -> http_persistent )
av_dict_set ( & opts , "multiple_requests" , "1" , 0 );
if ( seg -> size >= 0 ) {
/* try to restrict the HTTP request to the part we want
* (if this is in fact a HTTP request) */
av_dict_set_int ( & opts , "offset" , seg -> url_offset , 0 );
av_dict_set_int ( & opts , "end_offset" , seg -> url_offset + seg -> size , 0 );
}
if ( c -> retry_segment_cnt < 0 ) {
if ( pls -> finished ) {
c -> http_always_reconnect = 1 ;
} else {
c -> http_always_reconnect = 0 ;
}
}
av_dict_set_int ( & c -> avio_opts , "http_always_reconnect" , c -> http_always_reconnect , 0 );
av_log ( NULL , AV_LOG_DEBUG , "HLS request for url '%s', offset %" PRId64 ", playlist %d \n " ,
trimLeadingCharacter ( seg -> url ), seg -> url_offset , pls -> index );
if ( seg -> key_type == KEY_NONE ) {
ret = open_url ( pls -> parent , in , seg -> url , c -> avio_opts , opts , & is_http );
} else if ( seg -> key_type == KEY_AES_128 ) {
AVDictionary * opts2 = NULL ;
char iv [ 33 ], key [ 33 ], url [ MAX_URL_SIZE ];
if ( strcmp ( seg -> key , pls -> key_url )) {
AVIOContext * pb = NULL ;
if ( open_url ( pls -> parent , & pb , seg -> key , c -> avio_opts , opts , NULL ) == 0 ) {
ret = avio_read ( pb , pls -> key , sizeof ( pls -> key ));
if ( ret != sizeof ( pls -> key )) {
av_log ( NULL , AV_LOG_ERROR , "Unable to read key file %s \n " ,
seg -> key );
}
ff_format_io_close ( pls -> parent , & pb );
} else {
av_log ( NULL , AV_LOG_ERROR , "Unable to open key file %s \n " ,
seg -> key );
}
av_strlcpy ( pls -> key_url , seg -> key , sizeof ( pls -> key_url ));
}
ff_data_to_hex ( iv , seg -> iv , sizeof ( seg -> iv ), 0 );
ff_data_to_hex ( key , pls -> key , sizeof ( pls -> key ), 0 );
iv [ 32 ] = key [ 32 ] = '\0' ;
if ( strstr ( seg -> url , "://" ))
snprintf ( url , sizeof ( url ), "crypto+%s" , seg -> url );
else
snprintf ( url , sizeof ( url ), "crypto:%s" , seg -> url );
av_dict_copy ( & opts2 , c -> avio_opts , 0 );
av_dict_set ( & opts2 , "key" , key , 0 );
av_dict_set ( & opts2 , "iv" , iv , 0 );
ret = open_url ( pls -> parent , in , url , opts2 , opts , & is_http );
av_dict_free ( & opts2 );
if ( ret < 0 ) {
goto cleanup ;
}
ret = 0 ;
} else if ( seg -> key_type == KEY_SAMPLE_AES || seg -> key_type == KEY_SAMPLE_AES_CTR ) {
// av_log(NULL, AV_LOG_ERROR,"SAMPLE-AES encryption is not supported yet\n");
ret = open_url ( pls -> parent , in , seg -> url , c -> avio_opts , opts , & is_http );
// ret = AVERROR_PATCHWELCOME;
}
else
ret = AVERROR ( ENOSYS );
/* Seek to the requested position. If this was a HTTP request, the offset
* should already be where want it to, but this allows e.g. local testing
* without a HTTP server.
*
* This is not done for HTTP at all as avio_seek() does internal bookkeeping
* of file offset which is out-of-sync with the actual offset when "offset"
* AVOption is used with http protocol, causing the seek to not be a no-op
* as would be expected. Wrong offset received from the server will not be
* noticed without the call, though.
*/
if ( ret == 0 && ! is_http && seg -> key_type == KEY_NONE && seg -> url_offset ) {
int64_t seekret = avio_seek ( * in , seg -> url_offset , SEEK_SET );
if ( seekret < 0 ) {
av_log ( NULL , AV_LOG_ERROR , "Unable to seek to offset %" PRId64 " of HLS segment '%s' \n " , seg -> url_offset , seg -> url );
ret = seekret ;
ff_format_io_close ( pls -> parent , in );
}
}
cleanup :
av_dict_free ( & opts );
pls -> cur_seg_offset = 0 ;
return ret ;
}
static int hls_check_requested ( struct playlist * pls )
{
if ( ! pls )
return 0 ;
if ( pls -> reopen_playlist_requested
|| pls -> switch_variant_requested != - 1 ) {
return AVERROR_EOF ;
}
return 0 ;
}
static int update_init_section ( struct playlist * pls , struct segment * seg )
{
static const int max_init_section_size = 1024 * 1024 ;
HLSContext * c = pls -> parent -> priv_data ;
int64_t sec_size ;
int64_t urlsize ;
int ret ;
if (( seg -> init_section == pls -> cur_init_section
|| ( seg -> init_section && pls -> cur_init_section && ! strcmp ( seg -> init_section -> url , pls -> cur_init_section -> url )))
&& pls -> init_section_update_status == INIT_SEC_UPDATE_NONE )
return 0 ;
if ( pls -> cur_init_section ) {
switch ( pls -> init_section_update_status ) {
case INIT_SEC_UPDATE_NONE :
pls -> init_section_update_status = INIT_SEC_UPDATE_WAITING_REOPEN ;
case INIT_SEC_UPDATE_WAITING_REOPEN :
av_log ( 0 , AV_LOG_INFO , "wait for reopen pls->ctx,pls->cur_init_section:%s \n " , trimLeadingCharacter ( pls -> cur_init_section -> url ));
return AVERROR_EOF ;
case INIT_SEC_UPDATE_REOPEN_DONE :
default :
av_log ( 0 , AV_LOG_INFO , "update init section \n " );
pls -> init_section_update_status = INIT_SEC_UPDATE_NONE ;
}
}
pls -> cur_init_section = NULL ;
if ( ! seg -> init_section )
return 0 ;
ret = open_input ( c , pls , seg -> init_section , & pls -> input );
if ( ret < 0 ) {
av_log ( NULL , AV_LOG_WARNING , "Failed to open an initialization section in playlist %d, ret:%d \n " ,
pls -> index , ret );
c -> read_data_error = ret ;
return ret ;
}
if ( seg -> init_section -> size >= 0 )
sec_size = seg -> init_section -> size ;
else if (( urlsize = avio_size ( pls -> input )) >= 0 )
sec_size = urlsize ;
else
sec_size = max_init_section_size ;
av_log ( NULL , AV_LOG_INFO , "Downloading an initialization section of size %" PRId64 " \n " ,
sec_size );
sec_size = FFMIN ( sec_size , max_init_section_size );
av_fast_malloc ( & pls -> init_sec_buf , & pls -> init_sec_buf_size , sec_size );
ret = read_from_url ( pls , seg -> init_section , pls -> init_sec_buf ,
pls -> init_sec_buf_size , READ_COMPLETE );
ff_format_io_close ( pls -> parent , & pls -> input );
if ( ret < 0 ) {
av_log ( 0 , AV_LOG_ERROR , "read_from_url ret:%d \n " , ret );
c -> read_data_error = ret ;
return ret ;
}
pls -> cur_init_section = seg -> init_section ;
pls -> init_sec_data_len = ret ;
pls -> init_sec_buf_read_offset = 0 ;
/* spec says audio elementary streams do not have media initialization
* sections, so there should be no ID3 timestamps */
pls -> is_id3_timestamped = 0 ;
return 0 ;
}
static int64_t default_reload_interval ( struct playlist * pls )
{
return pls -> n_segments > 0 ?
pls -> segments [ pls -> n_segments - 1 ] -> duration :
pls -> target_duration ;
}
static int playlist_needed ( struct playlist * pls )
{
AVFormatContext * s = pls -> parent ;
int i , j ;
int stream_needed = 0 ;
int first_st ;
/* If there is no context or streams yet, the playlist is needed */
if ( ! pls -> ctx || ! pls -> n_main_streams )
return 1 ;
/* check if any of the streams in the playlist are needed */
for ( i = 0 ; i < pls -> n_main_streams ; i ++ ) {
if ( pls -> main_streams [ i ] -> discard < AVDISCARD_ALL ) {
stream_needed = 1 ;
break ;
}
}
/* If all streams in the playlist were discarded, the playlist is not
* needed (regardless of whether whole programs are discarded or not). */
if ( ! stream_needed )
return 0 ;
/* Otherwise, check if all the programs (variants) this playlist is in are
* discarded. Since all streams in the playlist are part of the same programs
* we can just check the programs of the first stream. */
first_st = pls -> main_streams [ 0 ] -> index ;
for ( i = 0 ; i < s -> nb_programs ; i ++ ) {
AVProgram * program = s -> programs [ i ];
if ( program -> discard < AVDISCARD_ALL ) {
for ( j = 0 ; j < program -> nb_stream_indexes ; j ++ ) {
if ( program -> stream_index [ j ] == first_st ) {
/* playlist is in an undiscarded program */
return 1 ;
}
}
}
}
/* some streams were not discarded but all the programs were */
return 0 ;
}
#define kNeedReopenPlaylistCount (99)
static int read_data ( void * opaque , uint8_t * buf , int buf_size )
{
struct playlist * v = opaque ;
HLSContext * c = v -> parent -> priv_data ;
int ret ;
int just_opened = 0 ;
int reload_count = 0 ;
struct segment * seg ;
/*seg is not open ,record the time for timeout*/
if ( v -> input == NULL )
v -> fetch_start = av_gettime_relative ();
/*Now ensure that the read_data is successful, and if it fails, it will exit the playback*/
if ( c -> read_data_error ) {
char errbuf [ 50 ] = { 0 };
av_strerror ( c -> read_data_error , errbuf , sizeof ( errbuf ));
av_log ( NULL , AV_LOG_INFO , "error!!! read_data_error:%d,EOF:%d, %s" , c -> read_data_error , AVERROR_EOF , errbuf );
return c -> read_data_error ;
}
/* read_date must bee nonbock , so sleep in player thread,
switch bandwidth and read frame or read data use the same lock,
so read data should quickly as soon as possible*/
if ( c -> expire_time > av_gettime_relative ()) {
av_log ( NULL , AV_LOG_VERBOSE , "error--------------------->55555timeout \n " );
return AVERROR ( ETIMEDOUT );
}
restart :
if ( ! v -> needed )
return AVERROR_EOF ;
if ( ! v -> input || ( c -> http_persistent && v -> input_read_done )) {
int64_t reload_interval ;
/* Check that the playlist is still needed before opening a new
* segment. */
v -> needed = playlist_needed ( v );
if ( ! v -> needed ) {
av_log ( NULL , AV_LOG_INFO , "No longer receiving playlist %d \n " ,
v -> index );
return AVERROR_EOF ;
}
/* If this is a live stream and the reload interval has elapsed since
* the last playlist reload, reload the playlists now. */
reload_interval = default_reload_interval ( v );
reload :
reload_count ++ ;
if ( reload_count > c -> max_reload )
return AVERROR_EOF ;
if ( c -> hls_thread_enable )
{
if ( v -> finished )
{
}
if ( v -> playlist_pb && ! v -> finished )
{
if ( c -> hlsLiveMode == HLS_MODE_LIVE_SHIFT && v -> timeshif_url )
{
memcpy ( v -> url , v -> timeshif_url , sizeof ( v -> url ) - 1 );
}
ret = parse_playlist ( c , v -> url , v , v -> playlist_pb );
if ( ret < 0 ) {
if ( ret != AVERROR_EXIT )
{
av_log ( NULL , AV_LOG_WARNING , "Failed to reload playlist_pb %d \n " ,
v -> index );
}
}
ff_format_io_close ( c -> ctx , & v -> playlist_pb );
v -> playlist_pb = NULL ;
if ( ret < 0 ) {
return ret ;
}
reload_interval = v -> target_duration / 2 ;
} else {
if ( ! v -> finished && v -> playlist_pb == NULL &&
( av_gettime_relative () - v -> last_load_time >= reload_interval ) &&
c -> net_down_wait_parse_playlist ) {
return AVERROR ( EAGAIN ); /* wait network up and flush playlist ok */
}
//return AVERROR(ETIMEDOUT);
}
}
else if ( ! v -> finished &&
av_gettime_relative () - v -> last_load_time >= reload_interval ) {
if (( ret = parse_playlist ( c , v -> url , v , NULL )) < 0 ) {
if ( ret != AVERROR_EXIT )
av_log ( NULL , AV_LOG_WARNING , "Failed to reload playlist %d \n " ,
v -> index );
return ret ;
}
/* If we need to reload the playlist again below (if
* there's still no more segments), switch to a reload
* interval of half the target duration. */
reload_interval = v -> target_duration / 2 ;
}
if ( v -> cur_seq_no < v -> start_seq_no ) {
av_log ( NULL , AV_LOG_WARNING , "skipping %d segments ahead, expired from playlists \n " ,
v -> start_seq_no - v -> cur_seq_no );
v -> cur_seq_no = v -> start_seq_no ;
}
if ( v -> cur_seq_no >= v -> start_seq_no + v -> n_segments ) {
av_log ( NULL , AV_LOG_VERBOSE , "reach the latest segment start:%d cur:%d, n_segments:%d, finish:%d, buffer:%d \n " ,
v -> start_seq_no , v -> cur_seq_no , v -> n_segments , v -> finished , v -> pb . buf_end - v -> pb . buf_ptr );
if ( v -> finished )
{
hls_bw_eof ( c -> hlsBandWidth );
return AVERROR_EOF ;
}
if ( c -> hls_thread_enable ) {
if ( v -> cur_seq_no > v -> start_seq_no + v -> n_segments + 1 ) {
av_log ( NULL , AV_LOG_WARNING , "cur_seq_no:%d >= start_seq_no:%d + segments:%d \n " ,
v -> cur_seq_no , v -> start_seq_no , v -> n_segments + 1 );
v -> cur_seq_no = v -> start_seq_no ;
} else {
//TODO
//sleep not good
//use sleep with interrupt or del it
//av_usleep(20*1000);
}
// return AVERROR(EAGAIN);
if ( v -> pb . buf_end - v -> pb . buf_ptr > 0 )
return AVERROR ( EAGAIN );
while ( 1 ) {
if ( ff_check_interrupt ( c -> interrupt_callback ))
return AVERROR_EXIT ;
c -> m3u8UpdateFunc ( c -> parserHandle , 0 );
if ( v -> playlist_pb ) {
goto reload ;
} else if ( c -> read_data_error ) {
char errbuf [ 50 ] = { 0 };
av_strerror ( c -> read_data_error , errbuf , sizeof ( errbuf ));
av_log ( NULL , AV_LOG_INFO , "error!!! read_data_error:%d,EOF:%d, %s" , c -> read_data_error , AVERROR_EOF , errbuf );
return c -> read_data_error ;
} else {
av_usleep ( 1000 * 20 );
}
}
}
while ( av_gettime_relative () - v -> last_load_time < reload_interval ) {
if ( ff_check_interrupt ( c -> interrupt_callback ))
return AVERROR_EXIT ;
//this sleep unreachable
//call_AMLSleep_msleep((void *)c->AMLSleep, 100);
c -> expire_time = av_gettime_relative () + 100 * 1000 ;
}
/* Enough time has elapsed since the last reload */
goto reload ;
}
v -> input_read_done = 0 ;
seg = current_segment ( v );
/* load/update Media Initialization Section, if any */
ret = update_init_section ( v , seg );
if ( ret ) {
char errbuf [ 50 ] = { 0 };
av_strerror ( ret , errbuf , sizeof ( errbuf ));
av_log ( NULL , AV_LOG_VERBOSE , "update_init_section, ret:%d, %s, playlist:%p, cur_seq_no:%d, start_seq_no:%d, n_segments:%d, url:%s \n " , ret , errbuf , v , v -> cur_seq_no , v -> start_seq_no , v -> n_segments , trimLeadingCharacter ( seg -> url ));
return ret ;
}
/* 1.check reopen playlist, if any
2.check switch variant, if any
*/
ret = hls_check_requested ( v );
if ( ret ) {
av_log ( NULL , AV_LOG_INFO , "[HlsAdaptive] psl:%p, pls->reopen_playlist_requested:%d, switch_variant_requested:%d" , v , v -> reopen_playlist_requested , v -> switch_variant_requested );
return ret ;
}
#ifdef HLS_I_FRAME_MODE
if ( c -> http_multiple == 1 && v -> input_next_requested && ! c -> i_frame_mode )
#else
if ( c -> http_multiple == 1 && v -> input_next_requested )
#endif
{
FFSWAP ( AVIOContext * , v -> input , v -> input_next );
v -> input_next_requested = 0 ;
ret = 0 ;
av_log ( NULL , AV_LOG_INFO , "index:%d, replace input ts, pls:%p, cur_seq_no:%d, start_seq_no:%d, cur-start diff:%d, n_segments:%d, duration:%lld, url:%s" , v -> index , v , v -> cur_seq_no , v -> start_seq_no , v -> cur_seq_no - v -> start_seq_no , v -> n_segments , seg -> duration , trimLeadingCharacter ( seg -> url ));
} else {
av_log ( NULL , AV_LOG_INFO , "index:%d, open input ts, pls:%p, cur_seq_no:%d, start_seq_no:%d, cur-start diff:%d, n_segments:%d, duration:%lld, url:%s" , v -> index , v , v -> cur_seq_no , v -> start_seq_no , v -> cur_seq_no - v -> start_seq_no , v -> n_segments , seg -> duration , trimLeadingCharacter ( seg -> url ));
ret = open_input ( c , v , seg , & v -> input );
}
if ( ret < 0 ) {
if ( ff_check_interrupt ( c -> interrupt_callback ))
return AVERROR_EXIT ;
char errbuf [ 50 ] = { 0 };
int ret1 = 0 ;
if ( check_net_phy_conn_status () == 0 )
{
av_log ( NULL , AV_LOG_WARNING , "hls net down" );
set_dns_net_down ();
c -> net_down_wait_parse_playlist = 1 ;
return AVERROR_NET_NEED_RECONNECT ;
}
//refresh m3u8
if ( ret == AVERROR_HTTP_FORBIDDEN ||
ret == AVERROR_HTTP_NOT_FOUND ||
ret == AVERROR_HTTP_OTHER_4XX ||
ret == AVERROR_HTTP_SERVER_ERROR ) {
av_strerror ( ret , errbuf , sizeof ( errbuf ));
av_log ( NULL , AV_LOG_WARNING , "Failed err:%s, need to refresh pls %d of playlist %d, ,url:%s \n " ,
errbuf ,
v -> cur_seq_no ,
v -> index ,
v -> url );
if ( c -> retry_segment_cnt < 0 && v -> finished ) {
v -> cur_seq_no ++ ;
c -> cur_seq_no = v -> cur_seq_no ;
v -> open_retrytime = 0 ;
return AVERROR ( EAGAIN );
} else {
if ( v -> redirect_url )
av_freep ( & v -> redirect_url );
if (( ret1 = parse_playlist ( c , v -> url , v , NULL )) < 0 ) {
av_strerror ( ret1 , errbuf , sizeof ( errbuf ));
av_log ( NULL , AV_LOG_WARNING , "Failed to refresh pls %d of playlist %d, errbuf:%s,url:%s \n " ,
v -> cur_seq_no ,
v -> index ,
errbuf ,
v -> url );
}
}
}
if ( c -> retry_segment_cnt < 0 ) { /* -1 is reopen this segment forever */
return AVERROR_NET_NEED_RECONNECT ;
}
c -> expire_time = av_gettime_relative () + 100 * 1000 ;
if ( ret < 0 ) {
//av_log(NULL, AV_LOG_VERBOSE,"open input ts again failed, ret:%d, add one seq then reload", ret);
//v->cur_seq_no += 1;
av_strerror ( ret , errbuf , sizeof ( errbuf ));
{
if ( c -> http_persistent &&
seg -> key_type == KEY_NONE && av_strstart ( seg -> url , "http" , NULL )) {
v -> input_read_done = 1 ;
} else {
ff_format_io_close ( v -> parent , & v -> input );
}
v -> open_retrytime ++ ;
av_log ( NULL , AV_LOG_VERBOSE , "open segment error:%s:retry:%d,cur_seq_no%d of playlist %d, start_seq_no:%d, n_segments:%d" , errbuf , v -> open_retrytime , v -> cur_seq_no , v -> index , v -> start_seq_no , v -> n_segments );
}
if ( v -> open_retrytime >= c -> retry_segment_cnt + 1 ) { /* retry_segment_cnt, 0:is not retry, open next seg */
v -> cur_seq_no ++ ;
c -> cur_seq_no = v -> cur_seq_no ;
v -> open_retrytime = 0 ;
av_log ( NULL , AV_LOG_VERBOSE , "open segment timeout, skip next,cur_seq_no%d, start_seq_no:%d, n_segments:%d" , v -> cur_seq_no , v -> start_seq_no , v -> n_segments );
}
return AVERROR ( ETIMEDOUT );
}
} else {
/* open sucess retry time clear*/
c -> net_down_wait_parse_playlist = 0 ;
v -> open_retrytime = 0 ;
}
just_opened = 1 ;
}
if ( c -> http_multiple == - 1 ) {
uint8_t * http_version_opt = NULL ;
int r = av_opt_get ( v -> input , "http_version" , AV_OPT_SEARCH_CHILDREN , & http_version_opt );
if ( r >= 0 ) {
c -> http_multiple = strncmp (( const char * ) http_version_opt , "1.1" , 3 ) == 0 ;
av_freep ( & http_version_opt );
}
}
#ifdef HLS_I_FRAME_MODE
if ( ! c -> i_frame_mode ) {
#endif
seg = next_segment ( v );
if ( c -> http_multiple == 1 && ! v -> input_next_requested &&
seg && av_strstart ( seg -> url , "http" , NULL )) {
av_log ( NULL , AV_LOG_INFO , "index:%d, open input_next, pls:%p, cur_seq_no:%d, start_seq_no:%d, n_segments:%d, url:%s." , v -> index , v , v -> cur_seq_no , v -> start_seq_no , v -> n_segments , seg -> url + 1 );
ret = open_input ( c , v , seg , & v -> input_next );
if ( ret < 0 ) {
if ( ff_check_interrupt ( c -> interrupt_callback )) {
return AVERROR_EXIT ;
}
av_log ( NULL , AV_LOG_WARNING , "Failed to open segment %d of playlist %d \n " ,
v -> cur_seq_no + 1 ,
v -> index );
} else {
v -> input_next_requested = 1 ;
}
}
#ifdef HLS_I_FRAME_MODE
}
#endif
if ( v -> init_sec_buf_read_offset < v -> init_sec_data_len ) {
/* Push init section out first before first actual segment */
int copy_size = FFMIN ( v -> init_sec_data_len - v -> init_sec_buf_read_offset , buf_size );
memcpy ( buf , v -> init_sec_buf , copy_size );
v -> init_sec_buf_read_offset += copy_size ;
return copy_size ;
}
//BW
#ifdef HLS_I_FRAME_MODE
if ( c -> n_variants > 1 && ( c -> quality == 2 || c -> quality == 3 ) && ! c -> i_frame_mode )
#else
if ( c -> n_variants > 1 && ( c -> quality == 2 || c -> quality == 3 ))
#endif
{
// if (!hls_bw_ready_read(c->ctx, v))
{
//playable_msec has enough,
//so sleep here
if ( ff_check_interrupt ( c -> interrupt_callback ))
return AVERROR_EXIT ;
//TODO
//sleep not good
//use sleep with interrupt
//av_usleep(20*1000);
// return AVERROR(ETIMEDOUT);
}
}
seg = current_segment ( v );
ret = read_from_url ( v , seg , buf , buf_size , READ_NORMAL );
if ( ret > 0 ) {
if (( just_opened || v -> parse_preloaded_segment_id3_requested ) && v -> is_id3_timestamped != 0 ) {
/* Intercept ID3 tags here, elementary audio streams are required
* to convey timestamps using them in the beginning of each segment. */
intercept_id3 ( v , buf , buf_size , & ret );
if ( v -> parse_preloaded_segment_id3_requested ) {
v -> parse_preloaded_segment_id3_requested = 0 ;
av_log ( NULL , AV_LOG_VERBOSE , "intercept_id3 done, parse_preloaded_segment_id3_requested:%d" , v -> parse_preloaded_segment_id3_requested );
}
}
v -> lastread_time = av_gettime_relative ();
return ret ;
}
char errbuf [ 50 ] = { 0 };
av_strerror ( ret , errbuf , sizeof ( errbuf ));
av_log ( NULL , AV_LOG_VERBOSE , "read_from_url, ret:%d,EOF:%d, %s, playlist:%p" , ret , AVERROR_EOF , errbuf , v );
int64_t average_speed = - 1 , last_speed = - 1 ;
hls_get_ts_speed ( v -> input , & average_speed , & last_speed );
//BW
int ret_bw = - 1 ;
int64_t time = av_gettime_relative ();
int islive = ( v -> finished == 1 ) ? 0 : 1 ;
if ( ret == AVERROR_EOF )
{
if ( c -> http_persistent &&
seg -> key_type == KEY_NONE && av_strstart ( seg -> url , "http" , NULL )) {
v -> input_read_done = 1 ;
} else {
ff_format_io_close ( v -> parent , & v -> input );
}
#ifdef HLS_I_FRAME_MODE
judge_next_segment ( v );
#else
v -> cur_seq_no ++ ;
#endif
c -> cur_seq_no = v -> cur_seq_no ;
#ifdef HLS_I_FRAME_MODE
if ( c -> n_variants > 1 && ( c -> quality == 2 || c -> quality == 3 ) && ! c -> i_frame_mode )
#else
if ( c -> n_variants > 1 && ( c -> quality == 2 || c -> quality == 3 ))
#endif
{
//make sure v is the main media
struct variant_t * variant = c -> variants [ c -> varrant_used ];
if ( variant -> playlists [ 0 ] == v )
{
if ( v -> cur_seq_no >= v -> start_seq_no + v -> n_segments ) {
if ( v -> finished ) {
hls_bw_eof ( c -> hlsBandWidth );
}
}
ret_bw = hls_bw_finish_one_ts ( c -> ctx , average_speed , last_speed );
}
}
{
if ( v -> reopen_playlist_enable ) {
++ v -> each_segment_read_done ;
if ( v -> each_segment_read_done > kNeedReopenPlaylistCount )
v -> reopen_playlist_requested = 1 ;
if ( v -> each_segment_read_done % 10 == 0 )
av_log ( NULL , AV_LOG_INFO , "playlist:%p, enable:%d, each_segment_read_done:%d, reopen_playlist_requested:%d" , v , v -> reopen_playlist_enable , v -> each_segment_read_done , v -> reopen_playlist_requested );
} else {
v -> each_segment_read_done = 0 ;
}
if ( ret_bw >= 0 ) {
//disable switch_variant
v -> switch_variant_requested = ret_bw ;
av_log ( NULL , AV_LOG_INFO , "%s %d, set switch variant requested=%d" , __FUNCTION__ , __LINE__ , v -> switch_variant_requested );
}
}
av_log ( NULL , AV_LOG_VERBOSE , "eof, skip next,cur_seq_no:%d, start_seq_no:%d, n_segments:%d, ret_bw:%d, playlist:%p, switch_variant_requested:%d" , v -> cur_seq_no , v -> start_seq_no , v -> n_segments , ret_bw , v , v -> switch_variant_requested );
}
else if ( ret == AVERROR ( EAGAIN ))
{
if ( c -> retry_segment_cnt < 0 && islive == 0 ) { /* -1 is reopen this segment forever */
return AVERROR_NET_NEED_RECONNECT ;
}
if (( time - v -> lastread_time ) / 1000000 > 5 )
{
if ( c -> http_persistent &&
seg -> key_type == KEY_NONE && av_strstart ( seg -> url , "http" , NULL )) {
v -> input_read_done = 1 ;
} else {
ff_format_io_close ( v -> parent , & v -> input );
v -> read_retrytime ++ ;
}
}
av_log ( NULL , AV_LOG_ERROR , "EAGAIN, skip next,cur_seq_no:%d, start_seq_no:%d, n_segments:%d" , v -> cur_seq_no , v -> start_seq_no , v -> n_segments );
//call_AMLSleep_msleep((void *)c->AMLSleep, 100);
c -> expire_time = time + 100 * 1000 ;
c -> read_data_error = ret ;
}
else
{
if ( c -> retry_segment_cnt < 0 && islive == 0 ) { /* -1 is reopen this segment forever */
return AVERROR_NET_NEED_RECONNECT ;
}
if ( c -> http_persistent &&
seg -> key_type == KEY_NONE && av_strstart ( seg -> url , "http" , NULL )) {
v -> input_read_done = 1 ;
} else {
ff_format_io_close ( v -> parent , & v -> input );
}
v -> read_retrytime ++ ;
c -> expire_time = time + 100 * 1000 ;
c -> read_data_error = ret ;
av_log ( NULL , AV_LOG_ERROR , "read error:%d, cur_seq_no%d, start_seq_no:%d, n_segments:%d, url:%s" , ret , v -> cur_seq_no , v -> start_seq_no , v -> n_segments , trimLeadingCharacter ( seg -> url ));
}
if ( v -> read_retrytime >= c -> retry_segment_cnt + 1 && c -> retry_segment_cnt >= 0 ) {
v -> cur_seq_no ++ ;
c -> cur_seq_no = v -> cur_seq_no ;
v -> read_retrytime = 0 ;
av_log ( NULL , AV_LOG_VERBOSE , "retry timeout, skip next,cur_seq_no:%d, start_seq_no:%d, n_segments:%d" , v -> cur_seq_no , v -> start_seq_no , v -> n_segments );
if ( c -> hls_notify != NULL )
c -> hls_notify (( void * ) c -> session , ffObLOOP_ERROR , NET_ERROR_GET_DATA_TIMEOUT , 0 , 0 );
#ifdef HLS_I_FRAME_MODE
if ( c -> n_variants > 1 && ( c -> quality == 2 || c -> quality == 3 ) && ! c -> i_frame_mode )
#else
if ( c -> n_variants > 1 && ( c -> quality == 2 || c -> quality == 3 ))
#endif
{
//make sure v is the main media
struct variant_t * variant = c -> variants [ c -> varrant_used ];
if ( variant -> playlists [ 0 ] == v )
{
if ( v -> cur_seq_no >= v -> start_seq_no + v -> n_segments ) {
if ( v -> finished ) {
hls_bw_eof ( c -> hlsBandWidth );
}
}
ret_bw = hls_bw_finish_one_ts ( c -> ctx , average_speed , last_speed );
}
}
}
if ( c -> expire_time > time )
{
return AVERROR ( ETIMEDOUT );
}
goto restart ;
}
static void add_renditions_to_variant ( HLSContext * c , struct variant_t * var ,
enum AVMediaType type , const char * group_id )
{
int i ;
for ( i = 0 ; i < c -> n_renditions ; i ++ ) {
struct rendition * rend = c -> renditions [ i ];
if ( rend -> type == type && ! strcmp ( rend -> group_id , group_id )) {
if ( rend -> playlist )
/* rendition is an external playlist
* => add the playlist to the variant */
dynarray_add ( & var -> playlists , & var -> n_playlists , rend -> playlist );
else
/* rendition is part of the variant main Media Playlist
* => add the rendition to the main Media Playlist */
dynarray_add ( & var -> playlists [ 0 ] -> renditions ,
& var -> playlists [ 0 ] -> n_renditions ,
rend );
}
}
}
static void add_metadata_from_renditions ( AVFormatContext * s , struct playlist * pls ,
enum AVMediaType type )
{
int rend_idx = 0 ;
int i ;
for ( i = 0 ; i < pls -> n_main_streams ; i ++ ) {
AVStream * st = pls -> main_streams [ i ];
if ( st -> codecpar -> codec_type != type )
continue ;
for (; rend_idx < pls -> n_renditions ; rend_idx ++ ) {
struct rendition * rend = pls -> renditions [ rend_idx ];
if ( rend -> type != type )
continue ;
if ( rend -> language [ 0 ])
av_dict_set ( & st -> metadata , "language" , rend -> language , 0 );
if ( rend -> name [ 0 ])
av_dict_set ( & st -> metadata , "comment" , rend -> name , 0 );
st -> disposition |= rend -> disposition ;
}
if ( rend_idx >= pls -> n_renditions )
break ;
}
}
/* if timestamp was in valid range: returns 1 and sets seq_no
* if not: returns 0 and sets seq_no to closest segment */
static int find_timestamp_in_playlist ( HLSContext * c , struct playlist * pls ,
int64_t timestamp , int * seq_no )
{
int i ;
int64_t pos = c -> first_timestamp == AV_NOPTS_VALUE ?
0 : c -> first_timestamp ;
if ( timestamp < pos ) {
* seq_no = pls -> start_seq_no ;
return 0 ;
}
for ( i = 0 ; i < pls -> n_segments ; i ++ ) {
int64_t diff = pos + pls -> segments [ i ] -> duration - timestamp ;
if ( diff > 0 ) {
* seq_no = pls -> start_seq_no + i ;
return 1 ;
}
pos += pls -> segments [ i ] -> duration ;
}
* seq_no = pls -> start_seq_no + pls -> n_segments - 1 ;
return 0 ;
}
static int select_cur_seq_no ( HLSContext * c , struct playlist * pls )
{
int seq_no ;
if ( ! c -> hls_thread_enable )
if ( ! pls -> finished && ! c -> first_packet &&
av_gettime_relative () - pls -> last_load_time >= default_reload_interval ( pls ))
/* reload the playlist since it was suspended */
parse_playlist ( c , pls -> url , pls , NULL );
/* If playback is already in progress (we are just selecting a new
* playlist) and this is a complete file, find the matching segment
* by counting durations. */
if ( pls -> finished && c -> cur_timestamp != AV_NOPTS_VALUE ) {
find_timestamp_in_playlist ( c , pls , c -> cur_timestamp , & seq_no );
return seq_no ;
}
if ( ! pls -> finished ) {
if ( ! c -> first_packet && /* we are doing a segment selection during playback */
c -> cur_seq_no >= pls -> start_seq_no &&
c -> cur_seq_no < pls -> start_seq_no + pls -> n_segments )
/* While spec 3.4.3 says that we cannot assume anything about the
* content at the same sequence number on different playlists,
* in practice this seems to work and doing it otherwise would
* require us to download a segment to inspect its timestamps. */
return c -> cur_seq_no ;
if ( c -> hlsLiveMode == HLS_MODE_LIVE )
{
/* If this is a live stream, start live_start_index segments from the
* start or end */
if ( c -> live_start_index < 0 )
{
av_log ( NULL , AV_LOG_INFO , "start no:%d, select_cur_seq_no: %d \n " , pls -> start_seq_no , pls -> start_seq_no + FFMAX ( pls -> n_segments + c -> live_start_index , 0 ));
return pls -> start_seq_no + FFMAX ( pls -> n_segments + c -> live_start_index , 0 );
}
else
{
av_log ( NULL , AV_LOG_INFO , "start no:%d, select_cur_seq_no: %d \n " , pls -> start_seq_no , pls -> start_seq_no + FFMIN ( c -> live_start_index , pls -> n_segments - 1 ));
return pls -> start_seq_no + FFMIN ( c -> live_start_index , pls -> n_segments - 1 );
}
}
}
/* Otherwise just start on the first segment. */
av_log ( NULL , AV_LOG_INFO , "select_cur_seq_no: %d \n " , pls -> start_seq_no );
return pls -> start_seq_no ;
}
static int save_avio_options ( AVFormatContext * s )
{
HLSContext * c = s -> priv_data ;
static const char * const opts [] = {
"headers" , "http_proxy" , "user_agent" , "user-agent" , "cookies" , "referer" , NULL };
const char * const * opt = opts ;
uint8_t * buf ;
int ret = 0 ;
while ( * opt ) {
if ( av_opt_get ( s -> pb , * opt , AV_OPT_SEARCH_CHILDREN | AV_OPT_ALLOW_NULL , & buf ) >= 0 ) {
ret = av_dict_set ( & c -> avio_opts , * opt , buf ,
AV_DICT_DONT_STRDUP_VAL );
if ( ret < 0 )
return ret ;
}
opt ++ ;
}
//for ts opt
av_dict_set_int ( & c -> avio_opts , "seekable" , c -> seekable , 0 );
av_dict_set_int ( & c -> avio_opts , "http_reconnect" , c -> http_reconnect , 0 );
av_dict_set_int ( & c -> avio_opts , "http_reconnect_at_eof" , c -> http_reconnect_at_eof , 0 );
av_dict_set_int ( & c -> avio_opts , "http_reconnect_streamed" , c -> http_reconnect_streamed , 0 );
av_dict_set_int ( & c -> avio_opts , "http_reconnect_delay_max" , c -> http_reconnect_delay_max , 1 );
av_dict_set_int ( & c -> avio_opts , "http_always_reconnect" , c -> http_always_reconnect , 0 );
av_dict_set_int ( & c -> avio_opts , "http_thread_enable" , c -> http_thread_enable , 0 );
av_dict_set_int ( & c -> avio_opts , "http_thread_bufsize" , c -> http_thread_bufsize , 0 );
av_dict_set_int ( & c -> avio_opts , "tcp_rw_timeout" , c -> tcp_rw_timeout , 0 );
av_dict_set_int ( & c -> avio_opts , "tcp_connect_timeout" , c -> tcp_connect_timeout , 0 );
av_dict_set_int ( & c -> avio_opts , "addrinfo_one_by_one" , c -> addrinfo_one_by_one , 0 );
av_dict_set_int ( & c -> avio_opts , "dns_seq_mode" , c -> dns_seq_mode , 0 );
av_dict_set_int ( & c -> avio_opts , "addrinfo_timeout" , c -> addrinfo_timeout , 0 );
av_dict_set_int ( & c -> avio_opts , "dns_cache_timeout" , c -> dns_cache_timeout , 0 );
return ret ;
}
static int nested_io_open ( AVFormatContext * s , AVIOContext ** pb , const char * url ,
int flags , AVDictionary ** opts )
{
av_log ( NULL , AV_LOG_ERROR , "A HLS playlist item '%s' referred to an external file '%s'. "
"Opening this file was forbidden for security reasons \n " ,
s -> url , url );
return AVERROR ( EPERM );
}
static void add_stream_to_programs ( AVFormatContext * s , struct playlist * pls , AVStream * stream )
{
HLSContext * c = s -> priv_data ;
int i , j ;
int bandwidth = - 1 ;
struct variant_t * v = NULL ;
#ifdef HLS_I_FRAME_MODE
for ( i = 0 ; i < c -> n_variants + c -> n_i_frame_variants ; i ++ ) {
if ( i < c -> n_variants ) {
v = c -> variants [ i ];
} else {
v = c -> i_frame_variants [ i - c -> n_variants ];
}
for ( j = 0 ; j < v -> n_playlists ; j ++ ) {
if ( v -> playlists [ j ] != pls )
continue ;
av_program_add_stream_index ( s , i , stream -> index );
if ( bandwidth < 0 )
bandwidth = v -> bandwidth ;
else if ( bandwidth != v -> bandwidth )
bandwidth = - 1 ; /* stream in multiple variants with different bandwidths */
}
}
#else
for ( i = 0 ; i < c -> n_variants ; i ++ ) {
v = c -> variants [ i ];
for ( j = 0 ; j < v -> n_playlists ; j ++ ) {
if ( v -> playlists [ j ] != pls )
continue ;
av_program_add_stream_index ( s , i , stream -> index );
if ( bandwidth < 0 )
bandwidth = v -> bandwidth ;
else if ( bandwidth != v -> bandwidth )
bandwidth = - 1 ; /* stream in multiple variants with different bandwidths */
}
}
#endif
if ( bandwidth >= 0 )
av_dict_set_int ( & stream -> metadata , "variant_bitrate" , bandwidth , 0 );
}
static int set_stream_info_from_input_stream ( AVStream * st , struct playlist * pls , AVStream * ist )
{
int err ;
err = avcodec_parameters_copy ( st -> codecpar , ist -> codecpar );
if ( err < 0 )
return err ;
if ( pls -> is_id3_timestamped ) /* custom timestamps via id3 */
avpriv_set_pts_info ( st , 33 , 1 , MPEG_TIME_BASE );
else
avpriv_set_pts_info ( st , ist -> pts_wrap_bits , ist -> time_base . num , ist -> time_base . den );
st -> internal -> need_context_update = 1 ;
st -> id = ist -> id ;
{
// copy content protection info from ist to st
int origin_side_data_size = 0 ;
uint8_t * origin_side_data = av_stream_get_side_data ( ist , AV_PKT_DATA_ENCRYPTION_INIT_INFO , & origin_side_data_size );
if ( origin_side_data ) {
AVEncryptionInitInfo * new_encryption_init_info = av_encryption_init_info_get_side_data ( origin_side_data , origin_side_data_size );
// check if the side data is already added, if so, append the new one to the end
int old_side_data_size = 0 ;
uint8_t * old_side_data = av_stream_get_side_data ( st , AV_PKT_DATA_ENCRYPTION_INIT_INFO , & old_side_data_size );
AVEncryptionInitInfo * old_init_info = NULL ;
if ( old_side_data ) {
const int append_side_data = 0 ; // may useful in future
if ( append_side_data ) {
old_init_info = av_encryption_init_info_get_side_data ( old_side_data , old_side_data_size );
if ( old_init_info ) {
for ( AVEncryptionInitInfo * cur = old_init_info ;; cur = cur -> next ) {
if ( ! cur -> next ) {
cur -> next = new_encryption_init_info ;
break ;
}
}
new_encryption_init_info = old_init_info ;
}
}
}
size_t new_side_data_size = 0 ;
uint8_t * new_side_data = av_encryption_init_info_add_side_data ( new_encryption_init_info , & new_side_data_size );
av_stream_add_side_data ( st , AV_PKT_DATA_ENCRYPTION_INIT_INFO , new_side_data , new_side_data_size );
av_encryption_init_info_free ( new_encryption_init_info );
}
}
return 0 ;
}
/* add new subdemuxer streams to our context, if any */
static int update_streams_from_subdemuxer ( AVFormatContext * s , struct playlist * pls )
{
int err ;
while ( pls -> n_main_streams < pls -> ctx -> nb_streams ) {
int ist_idx = pls -> n_main_streams ;
AVStream * st = avformat_new_stream ( s , NULL );
AVStream * ist = pls -> ctx -> streams [ ist_idx ];
if ( ! st )
return AVERROR ( ENOMEM );
st -> id = pls -> index ;
dynarray_add ( & pls -> main_streams , & pls -> n_main_streams , st );
add_stream_to_programs ( s , pls , st );
err = set_stream_info_from_input_stream ( st , pls , ist );
if ( err < 0 )
return err ;
}
return 0 ;
}
static void update_noheader_flag ( AVFormatContext * s )
{
HLSContext * c = s -> priv_data ;
int flag_needed = 0 ;
int i ;
for ( i = 0 ; i < c -> n_playlists ; i ++ ) {
struct playlist * pls = c -> playlists [ i ];
if ( pls -> has_noheader_flag ) {
flag_needed = 1 ;
break ;
}
}
if ( flag_needed )
s -> ctx_flags |= AVFMTCTX_NOHEADER ;
else
s -> ctx_flags &= ~ AVFMTCTX_NOHEADER ;
}
static int hls_close ( AVFormatContext * s )
{
HLSContext * c = s -> priv_data ;
av_log ( NULL , AV_LOG_INFO , "hls_close: %d \n " , __LINE__ );
free_playlist_list ( c );
free_variant_list ( c );
free_rendition_list ( c );
av_dict_free ( & c -> avio_opts );
ff_format_io_close ( c -> ctx , & c -> playlist_pb );
hls_bw_del ( s );
// call_AMLSleep_Destroy((void *)c->AMLSleep);
return 0 ;
}
int hls_switch_variant ( AVFormatContext * s , int varrant_used )
{
HLSContext * c = s -> priv_data ;
int ret = - 1 ;
struct variant_t * variant = NULL ;
if ( c -> n_variants <= 1 || c -> n_variants < varrant_used )
{
av_log ( NULL , AV_LOG_ERROR , "hls_switch_variant err, n_variants:%d, set value:%d" ,
c -> n_variants , varrant_used );
return - 1 ;
}
int i ;
varrant_used = varrant_used < c -> lowest_valid_variant ? c -> lowest_valid_variant : varrant_used ;
int varrant_used_last = c -> varrant_used ;
if ( varrant_used_last == varrant_used ) {
av_log ( NULL , AV_LOG_ERROR , "hls_switch_variant same variant, do nothing, varrant_used_last:%d, varrant_used:%d" ,
varrant_used_last , varrant_used );
return - 1 ;
}
int cur_seq_no = c -> variants [ varrant_used_last ] -> playlists [ 0 ] -> cur_seq_no ;
c -> varrant_used = varrant_used ;
variant = c -> variants [ c -> varrant_used ];
// {
// int playlists_num = c->n_playlists;
// av_log(NULL, AV_LOG_ERROR,"LNTES----------->enter hls_switch_variant varrant_used_last:%d, playlists_num:%d\n",varrant_used_last,playlists_num);
// for (int i = 0; i < playlists_num; i++)
// {
// reset_playlist(c, c->playlists[i]);
// }
// }
//close main input
{
/* Reset reading */
struct playlist * pls = c -> variants [ varrant_used_last ] -> playlists [ 0 ];
if ( pls -> input )
ff_format_io_close ( pls -> parent , & pls -> input );
pls -> input_read_done = 0 ;
pls -> open_retrytime = 0 ;
pls -> read_retrytime = 0 ;
if ( pls -> input_next )
ff_format_io_close ( pls -> parent , & pls -> input_next );
if ( pls -> playlist_pb )
ff_format_io_close ( c -> ctx , & pls -> playlist_pb );
pls -> input_next_requested = 0 ;
av_packet_unref ( & pls -> pkt );
reset_packet ( & pls -> pkt );
pls -> pb . eof_reached = 0 ;
/* Clear any buffered data */
pls -> pb . buf_end = pls -> pb . buf_ptr = pls -> pb . buffer ;
/* Reset the pos, to let the mpegts demuxer know we've seeked. */
pls -> pb . pos = 0 ;
/* Flush the packet queue of the subdemuxer. */
ff_read_frame_flush ( pls -> ctx );
pls -> seek_flags = 0 ;
pls -> needed = 0 ;
}
//clear playlists
for ( i = 0 ; i < c -> n_playlists_all ; i ++ )
{
c -> playlists [ i ] = NULL ;
}
//set new value
c -> n_playlists = variant -> n_playlists ;
for ( i = 0 ; i < variant -> n_playlists ; i ++ )
{
c -> playlists [ i ] = variant -> playlists [ i ];
}
//parse_playlist
//for (i = 0; i < c->n_playlists; i++)
{
//struct playlist *pls = c->playlists[i];
struct playlist * pls = variant -> playlists [ 0 ];
if (( ret = parse_playlist ( c , pls -> url , pls , NULL )) < 0 )
goto fail ;
}
/* Select the starting segments */
//for (i = 0; i < c->n_playlists; i++)
{
//struct playlist *pls = c->playlists[i];
struct playlist * pls = variant -> playlists [ 0 ];
pls -> cur_seq_no = cur_seq_no ;
pls -> needed = 1 ;
av_log ( NULL , AV_LOG_INFO , "hls_switch_variant c->n_playlists:%d, pls->cur_seq_no:%d, pls:%p, pls->input:%p" , c -> n_playlists , pls -> cur_seq_no , pls , pls -> input );
}
/* Open the demuxer for each playlist */
for ( i = 0 ; i < c -> n_playlists ; i ++ ) {
struct playlist * pls = c -> playlists [ i ];
AVInputFormat * in_fmt = NULL ;
av_log ( NULL , AV_LOG_VERBOSE , "hls_switch_variant enter, pls:%p, i:%d, url:%s" , pls , i , trimLeadingCharacter ( pls -> url ));
if ( pls -> ctx )
{
if ( i == 0 ) {
ret = reopen_pls ( s , pls );
if ( ret < 0 ) {
c -> read_data_error = AVERROR ( EIO ); //set err, pls restart
return ret ;
}
av_log ( NULL , AV_LOG_INFO , "hls_switch_variant reopen_pls, pls:%p, i:%d, url:%s" , pls , i , trimLeadingCharacter ( pls -> url ));
} else {
av_log ( NULL , AV_LOG_INFO , "hls_switch_variant continue, pls:%p, ctx:%p i:%d, url:%s" , pls , pls -> ctx , i , trimLeadingCharacter ( pls -> url ));
}
continue ;
}
av_log ( NULL , AV_LOG_VERBOSE , "hls_switch_variant start, pls:%p, ctx:%p, input:%p, i:%d, url:%s" , pls , pls -> ctx , pls -> input , i , trimLeadingCharacter ( pls -> url ));
if ( ! ( pls -> ctx = avformat_alloc_context ())) {
ret = AVERROR ( ENOMEM );
goto fail ;
}
if ( pls -> n_segments == 0 )
continue ;
pls -> index = i ;
pls -> needed = 1 ;
pls -> parent = s ;
pls -> ctx -> interrupt_callback = s -> interrupt_callback ;
pls -> switch_variant_requested = - 1 ;
pls -> read_buffer = av_malloc ( INITIAL_BUFFER_SIZE );
if ( ! pls -> read_buffer ) {
ret = AVERROR ( ENOMEM );
avformat_free_context ( pls -> ctx );
pls -> ctx = NULL ;
goto fail ;
}
ffio_init_context ( & pls -> pb , pls -> read_buffer , INITIAL_BUFFER_SIZE , 0 , pls ,
read_data , NULL , NULL );
//pls->pb.seekable = 0;
ret = av_probe_input_buffer ( & pls -> pb , & in_fmt , pls -> segments [ 0 ] -> url ,
NULL , 0 , 0 );
if ( ret < 0 ) {
/* Free the ctx - it isn't initialized properly at this point,
* so avformat_close_input shouldn't be called. If
* avformat_open_input fails below, it frees and zeros the
* context, so it doesn't need any special treatment like this. */
av_log ( NULL , AV_LOG_ERROR , "Error when loading first segment '%s' \n " , trimLeadingCharacter ( pls -> segments [ 0 ] -> url ));
avformat_free_context ( pls -> ctx );
pls -> ctx = NULL ;
goto fail ;
}
pls -> ctx -> pb = & pls -> pb ;
pls -> ctx -> io_open = nested_io_open ;
pls -> ctx -> flags |= s -> flags & ~ AVFMT_FLAG_CUSTOM_IO ;
if (( ret = ff_copy_whiteblacklists ( pls -> ctx , s )) < 0 )
goto fail ;
AVDictionary * opts = NULL ;
av_dict_set_int ( & opts , "hls_live_mode" , ! pls -> finished , 0 );
av_dict_set_int ( & opts , "sidx_reference_count" , 0 , 0 );
ret = avformat_open_input ( & pls -> ctx , pls -> segments [ 0 ] -> url , in_fmt , & opts );
av_dict_free ( & opts );
if ( ret < 0 )
goto fail ;
if ( pls -> id3_deferred_extra && pls -> ctx -> nb_streams == 1 ) {
ff_id3v2_parse_apic ( pls -> ctx , & pls -> id3_deferred_extra );
avformat_queue_attached_pictures ( pls -> ctx );
ff_id3v2_parse_priv ( pls -> ctx , & pls -> id3_deferred_extra );
ff_id3v2_free_extra_meta ( & pls -> id3_deferred_extra );
pls -> id3_deferred_extra = NULL ;
}
if ( pls -> is_id3_timestamped == - 1 )
av_log ( NULL , AV_LOG_WARNING , "No expected HTTP requests have been made \n " );
/*
* For ID3 timestamped raw audio streams we need to detect the packet
* durations to calculate timestamps in fill_timing_for_id3_timestamped_stream(),
* but for other streams we can rely on our user calling avformat_find_stream_info()
* on us if they want to.
*/
if ( pls -> is_id3_timestamped ) {
ret = avformat_find_stream_info ( pls -> ctx , NULL );
if ( ret < 0 )
goto fail ;
}
pls -> has_noheader_flag = !! ( pls -> ctx -> ctx_flags & AVFMTCTX_NOHEADER );
/* Create new AVStreams for each stream in this playlist */
ret = update_streams_from_subdemuxer ( s , pls );
if ( ret < 0 )
goto fail ;
/*
* Copy any metadata from playlist to main streams, but do not set
* event flags.
*/
if ( pls -> n_main_streams )
av_dict_copy ( & pls -> main_streams [ 0 ] -> metadata , pls -> ctx -> metadata , 0 );
add_metadata_from_renditions ( s , pls , AVMEDIA_TYPE_AUDIO );
add_metadata_from_renditions ( s , pls , AVMEDIA_TYPE_VIDEO );
add_metadata_from_renditions ( s , pls , AVMEDIA_TYPE_SUBTITLE );
av_log ( NULL , AV_LOG_VERBOSE , "hls_switch_variant done, pls:%p, pls->ctx:%p, input:%p, i:%d, url:%s" , pls , pls -> ctx , pls -> input , i , trimLeadingCharacter ( pls -> url ));
}
update_noheader_flag ( s );
return 0 ;
fail :
av_log ( NULL , AV_LOG_ERROR , "hls_switch_variant err" );
c -> varrant_used = varrant_used_last ;
variant = c -> variants [ c -> varrant_used ];
for ( i = 0 ; i < c -> n_playlists_all ; i ++ )
{
c -> playlists [ i ] = NULL ;
}
c -> n_playlists = variant -> n_playlists ;
for ( i = 0 ; i < variant -> n_playlists ; i ++ )
{
c -> playlists [ i ] = variant -> playlists [ i ];
}
return - 1 ;
}
static void mhls_notify ( void * handle , int msg , int64_t ext1 , int64_t ext2 , int64_t ext3 )
{
av_log ( NULL , AV_LOG_INFO , "msg=%d, ext1=%lld, ext2=%lld, ext3=%lld \n " , msg , ext1 , ext2 , ext3 );
if ( handle == NULL )
{
av_log ( NULL , AV_LOG_ERROR , "handle is null, return \n " );
return ;
}
if ( msg == ffObFFMPEG_PMT_CHANGE )
{
AVFormatContext * ctx = ( AVFormatContext * ) handle ;
struct playlist * pls = ( struct playlist * ) ctx -> parent ;
AVFormatContext * user_ctx = pls -> parent ;
HLSContext * c = user_ctx -> priv_data ;
if ( ! c -> hls_switch_pmt )
{
av_log ( NULL , AV_LOG_ERROR , "mhls_notify, pmt change, but do not support, hls_switch_pmt=%d \n " , c -> hls_switch_pmt );
return ;
}
int old_pls_n_main_streams = pls -> n_main_streams , ret = 0 , need_notify = 0 ;
update_streams_from_subdemuxer ( user_ctx , pls );
if ( old_pls_n_main_streams != pls -> n_main_streams )
{
av_log ( NULL , AV_LOG_ERROR , "num change, need notify \n " );
c -> index_defaule_max = user_ctx -> nb_streams - 1 ;
need_notify = 1 ;
}
for ( int i = 0 ; i < pls -> n_main_streams && i < pls -> ctx -> nb_streams ; i ++ )
{
AVStream * ist = pls -> ctx -> streams [ i ];
AVStream * st = pls -> main_streams [ i ];
if ( ist -> codecpar -> codec_id != st -> codecpar -> codec_id )
{
av_log ( NULL , AV_LOG_ERROR , "ctx_pid=%d codec_id=%d, pls_pid=%d, codec_id=%d" , ist -> id , ist -> codecpar -> codec_id , st -> id , st -> codecpar -> codec_id );
ret = set_stream_info_from_input_stream ( st , pls , ist );
if ( ret < 0 )
{
av_log ( NULL , AV_LOG_ERROR , "update info error" );
return ;
}
else
{
av_log ( NULL , AV_LOG_ERROR , "fmt change, need notify \n " );
need_notify = 1 ;
}
}
}
av_log ( NULL , AV_LOG_ERROR , "hls, old vpid=%d, apid=%d, spid=%d \n " , user_ctx -> streams [ c -> index_defaule_video ] -> id , user_ctx -> streams [ c -> index_defaule_audio ] -> id ,
user_ctx -> streams [ c -> index_defaule_sub ] -> id );
if (( c -> index_defaule_video != 0 && ext1 != user_ctx -> streams [ c -> index_defaule_video ] -> id ) ||
( c -> index_defaule_audio != 0 && ext2 != user_ctx -> streams [ c -> index_defaule_audio ] -> id ) ||
( c -> index_defaule_sub != 0 && ext3 != user_ctx -> streams [ c -> index_defaule_sub ] -> id ))
{
need_notify = 1 ;
for ( int i = 0 ; i < user_ctx -> nb_streams ; i ++ )
{
if ( user_ctx -> streams [ i ] -> id == ext1 )
c -> index_defaule_audio = user_ctx -> streams [ i ] -> index ;
if ( user_ctx -> streams [ i ] -> id == ext2 )
c -> index_defaule_video = user_ctx -> streams [ i ] -> index ;
if ( user_ctx -> streams [ i ] -> id == ext3 )
c -> index_defaule_sub = user_ctx -> streams [ i ] -> index ;
}
av_log ( NULL , AV_LOG_ERROR , "pid change, need notify \n " );
av_log ( NULL , AV_LOG_ERROR , "hls, new vpid=%d, apid=%d, spid=%d \n " , user_ctx -> streams [ c -> index_defaule_video ] -> id , user_ctx -> streams [ c -> index_defaule_audio ] -> id ,
user_ctx -> streams [ c -> index_defaule_sub ] -> id );
}
if ( c -> hls_notify && c -> session && need_notify && ( ext1 != 0 || ext2 != 0 || ext3 != 0 ))
{
ctx -> switching_param = ( ! ffchange_none );
av_log ( NULL , AV_LOG_INFO , "call hls_notify, pmt change, set hls_switching_param=%d, notify %d, vpid=%d, apid=%d, spid=%d \n " ,
ctx -> switching_param , ffObFFMPEG_PMT_CHANGE , ext1 , ext2 , ext3 );
c -> hls_notify ( c -> session , ffObFFMPEG_PMT_CHANGE , ext1 , ext2 , ext3 );
}
else
av_log ( NULL , AV_LOG_INFO , "m_vpid=%d, m_apid=%d, m_spid=%d, need_notify=%d, c->hls_notify is %s \n " , ext1 , ext2 , ext3 , need_notify , c -> hls_notify == NULL ? "null" : "not null" );
}
else
av_log ( NULL , AV_LOG_ERROR , "do not support this msg=%d \n " , msg );
return ;
}
static int reopen_pls ( AVFormatContext * s , struct playlist * pls )
{
av_log ( NULL , AV_LOG_INFO , "reopen_pls enter, pls:%p" , pls );
int ret = 0 ;
AVInputFormat * in_fmt = NULL ;
HLSContext * c = s -> priv_data ;
av_freep ( & pls -> pb . buffer );
avformat_close_input ( & pls -> ctx );
if ( ! ( pls -> ctx = avformat_alloc_context ())) {
ret = AVERROR ( ENOMEM );
return ret ;
}
pls -> init_section_update_status = INIT_SEC_UPDATE_REOPEN_DONE ;
pls -> ctx -> interrupt_callback = s -> interrupt_callback ;
pls -> switch_variant_requested = - 1 ;
pls -> read_buffer = av_malloc ( INITIAL_BUFFER_SIZE );
if ( ! pls -> read_buffer ) {
av_log ( NULL , AV_LOG_ERROR , "reopen_pls read_buffer malloc failed, ret:%d \n " , ret );
return AVERROR ( ENOMEM );
}
ffio_init_context ( & pls -> pb , pls -> read_buffer , INITIAL_BUFFER_SIZE , 0 , pls ,
read_data , NULL , NULL );
ret = av_probe_input_buffer ( & pls -> pb , & in_fmt , NULL ,
NULL , 0 , 0 );
if ( ret < 0 ) {
av_log ( NULL , AV_LOG_ERROR , "Error when av_probe_input_buffer, ret:%d \n " , ret );
return ret ;
}
pls -> ctx -> pb = & pls -> pb ;
pls -> ctx -> io_open = nested_io_open ;
pls -> ctx -> flags |= s -> flags & ~ AVFMT_FLAG_CUSTOM_IO ;
if ( c -> hls_switch_pmt )
pls -> ctx -> parent = ( void * ) pls ;
else
pls -> ctx -> parent = NULL ;
if (( ret = ff_copy_whiteblacklists ( pls -> ctx , s )) < 0 ) {
av_log ( NULL , AV_LOG_ERROR , "Error when copy whiteblacklists, ret:%d \n " , ret );
return ret ;
}
AVDictionary * opts = NULL ;
av_dict_set_int ( & opts , "hls_live_mode" , ! pls -> finished , 0 );
av_dict_set_int ( & opts , "sidx_reference_count" , 0 , 0 );
ret = avformat_open_input ( & pls -> ctx , NULL , in_fmt , & opts );
av_dict_free ( & opts );
if ( ret < 0 ) {
av_log ( NULL , AV_LOG_ERROR , "Error when avformat_open_input, ret:%d \n " , ret );
return ret ;
}
for ( int i = 0 ; i < pls -> ctx -> nb_streams && i < pls -> n_main_streams ; i ++ ) {
AVStream * st = pls -> main_streams [ i ];
AVStream * ist = pls -> ctx -> streams [ i ];
ret = set_stream_info_from_input_stream ( st , pls , ist );
if ( ret < 0 ) {
av_log ( NULL , AV_LOG_ERROR , "Error when set_stream_info_from_input_stream, ret:%d \n " , ret );
return ret ;
}
}
pls -> init_section_update_notify = 1 ;
av_log ( NULL , AV_LOG_ERROR , "reopen_pls leave" );
return ret ;
}
static int hls_read_header ( AVFormatContext * s )
{
void * u = ( s -> flags & AVFMT_FLAG_CUSTOM_IO ) ? NULL : s -> pb ;
HLSContext * c = s -> priv_data ;
int ret = 0 , i ;
int highest_cur_seq_no = 0 ;
c -> ctx = s ;
c -> interrupt_callback = & s -> interrupt_callback ;
c -> strict_std_compliance = s -> strict_std_compliance ;
c -> net_down_wait_parse_playlist = 0 ;
c -> first_packet = 1 ;
c -> i_frame_mode = 0 ;
c -> first_timestamp = AV_NOPTS_VALUE ;
c -> cur_timestamp = AV_NOPTS_VALUE ;
//add for amlogic, will change implament in the feauture, delete now
// c->AMLSleep = (void *)call_AMLSleep_Create();
c -> expire_time = av_gettime_relative ();
int64_t begin_time = c -> expire_time ;
c -> read_data_error = 0 ;
c -> hls_optimize_segment_timestamp_enable = am_getconfig_int_def ( "ffmpegctc.hls.OptSegTimestamp.enable" , 1 );
c -> index_defaule_audio = 0 ;
c -> index_defaule_sub = 0 ;
c -> index_defaule_video = 0 ;
av_log ( s , AV_LOG_INFO , "retry_segment_cnt:%d, OptSegTimestamp:%d" , c -> retry_segment_cnt , c -> hls_optimize_segment_timestamp_enable );
if ( u ) {
// get the previous user agent & set back to null if string size is zero
update_options ( & c -> user_agent , "user_agent" , u );
// get the previous cookies & set back to null if string size is zero
update_options ( & c -> cookies , "cookies" , u );
// get the previous headers & set back to null if string size is zero
update_options ( & c -> headers , "headers" , u );
// get the previous http proxt & set back to null if string size is zero
update_options ( & c -> http_proxy , "http_proxy" , u );
}
if (( ret = parse_playlist ( c , s -> url , NULL , s -> pb )) < 0 )
goto fail ;
if (( ret = save_avio_options ( s )) < 0 )
goto fail ;
/* Some HLS servers don't like being sent the range header */
//av_dict_set(&c->avio_opts, "seekable", "0", 0);
if ( c -> n_variants == 0 ) {
av_log ( NULL , AV_LOG_WARNING , "Empty playlist \n " );
ret = AVERROR_EOF ;
goto fail ;
}
//reorder variants
//0 is lowest,
if ( c -> n_variants > 1 )
{
for ( i = 0 ; i < c -> n_variants - 1 ; i ++ ) {
int j = 0 ;
for ( j = 0 ; j < c -> n_variants - 1 - i ; j ++ ) {
if ( c -> variants [ j ] -> bandwidth > c -> variants [ j + 1 ] -> bandwidth )
{
struct variant_t * v = c -> variants [ j + 1 ];
c -> variants [ j + 1 ] = c -> variants [ j ];
c -> variants [ j ] = v ;
}
}
}
}
int lowest_valid_variant = - 1 ;
/* Associate renditions with variants */
for ( i = 0 ; i < c -> n_variants ; i ++ ) {
struct variant_t * var = c -> variants [ i ];
if ( var -> audio_group [ 0 ])
add_renditions_to_variant ( c , var , AVMEDIA_TYPE_AUDIO , var -> audio_group );
if ( var -> video_group [ 0 ])
add_renditions_to_variant ( c , var , AVMEDIA_TYPE_VIDEO , var -> video_group );
if ( var -> subtitles_group [ 0 ])
add_renditions_to_variant ( c , var , AVMEDIA_TYPE_SUBTITLE , var -> subtitles_group );
if ( isCodecContainVideo ( var -> codecs )) {
var -> containVideo = 1 ;
if ( lowest_valid_variant == - 1 ) {
lowest_valid_variant = i ;
}
}
av_log ( NULL , AV_LOG_INFO , "after parse playlist index=%d, bandwidth=%dbps(need >= %dKBps), num playlist=%d, containvideo=%d, resolution=%s \n " ,
i , var -> bandwidth , var -> bandwidth / 1000 / 8 + 1 , var -> n_playlists , var -> containVideo , var -> resolution );
}
if ( lowest_valid_variant != - 1 ) {
c -> lowest_valid_variant = lowest_valid_variant ;
}
if ( c -> n_variants >= 1 )
{
if ( c -> quality == 0 ) {
c -> varrant_used = c -> n_variants - 1 ;
} else if ( c -> quality == 1 ) {
c -> varrant_used = 0 ;
} else if ( c -> quality == 2 ) {
c -> varrant_used = c -> n_variants - 1 ;
} else if ( c -> quality == 3 ) {
c -> varrant_used = 0 ;
}
c -> varrant_used = c -> varrant_used < c -> lowest_valid_variant ? c -> lowest_valid_variant : c -> varrant_used ;
struct variant_t * variant = c -> variants [ c -> varrant_used ];
int n_playlists = c -> n_playlists ;
c -> playlists_all = av_mallocz ( sizeof ( struct playlist * ) * n_playlists );
c -> n_playlists_all = n_playlists ;
for ( i = 0 ; i < n_playlists ; i ++ )
{
c -> playlists_all [ i ] = c -> playlists [ i ];
c -> playlists [ i ] = NULL ;
}
c -> n_playlists = variant -> n_playlists ;
for ( i = 0 ; i < variant -> n_playlists ; i ++ )
{
c -> playlists [ i ] = variant -> playlists [ i ];
}
}
/* If the playlist only contained playlists (Master Playlist),
* parse each individual playlist. */
if ( c -> n_playlists > 1 || c -> playlists [ 0 ] -> n_segments == 0 ) {
/*
* create pls->pb for each playlist
*/
int close_in = 0 ;
if ( c -> n_playlists > 1 || c -> playlists [ 0 ] -> n_segments == 0 ) {
for ( i = 0 ; i < c -> n_playlists ; i ++ ) {
struct playlist * pls = c -> playlists [ i ];
int is_http = av_strstart ( pls -> url , "http" , NULL );
AVDictionary * opts = NULL ;
get_m3u8_opts ( c , & opts );
//ret = c->ctx->io_open(c->ctx, &in, url, AVIO_FLAG_READ, &opts);
ret = amlio_open_whitelist ( & pls -> playlist_pb , pls -> url , AVIO_FLAG_READ , & c -> ctx -> interrupt_callback , & opts ,
c -> ctx -> protocol_whitelist , c -> ctx -> protocol_blacklist );
av_dict_free ( & opts );
if ( ret < 0 )
{
if ( check_net_phy_conn_status () == 0 )
{
av_log ( NULL , AV_LOG_WARNING , "hls net down" );
set_dns_net_down ();
ret = AVERROR ( ETIMEDOUT );
goto fail ;
}
if ( pls && pls -> redirect_url )
av_freep ( & pls -> redirect_url );
goto fail ;
}
if ( is_http && c -> http_persistent )
c -> playlist_pb = pls -> playlist_pb ;
else
close_in = 1 ;
}
}
for ( i = 0 ; i < c -> n_playlists ; i ++ ) {
struct playlist * pls = c -> playlists [ i ];
if (( ret = parse_playlist ( c , pls -> url , pls , pls -> playlist_pb )) < 0 )
goto fail ;
if ( close_in )
ff_format_io_close ( c -> ctx , & pls -> playlist_pb );
}
}
hls_bw_init ( s );
if ( c -> variants [ c -> varrant_used ] -> playlists [ 0 ] -> n_segments == 0 ) {
av_log ( NULL , AV_LOG_WARNING , "Empty playlist \n " );
ret = AVERROR_EOF ;
goto fail ;
}
/* If this isn't a live stream, calculate the total duration of the
* stream. */
if ( c -> variants [ c -> varrant_used ] -> playlists [ 0 ] -> finished ) {
int64_t duration = 0 ;
for ( i = 0 ; i < c -> variants [ c -> varrant_used ] -> playlists [ 0 ] -> n_segments ; i ++ )
duration += c -> variants [ c -> varrant_used ] -> playlists [ 0 ] -> segments [ i ] -> duration ;
s -> duration = duration ;
c -> hlsLiveMode = HLS_MODE_VOD ;
av_log ( NULL , AV_LOG_INFO , "set livemode %d \n " , c -> hlsLiveMode );
} else {
switch ( c -> customer_hls_mode )
{
case 1 :
c -> hlsLiveMode = HLS_MODE_LIVE ;
break ;
case 2 :
c -> hlsLiveMode = HLS_MODE_LIVE_SHIFT ;
break ;
default :
c -> hlsLiveMode = HLS_MODE_LIVE ;
break ;
}
av_log ( NULL , AV_LOG_INFO , "set livemode %d, c->live_start_index:%d \n " , c -> hlsLiveMode , c -> live_start_index );
}
#ifdef HLS_I_FRAME_MODE
/* Create a program for each variant */
for ( i = 0 ; i < c -> n_variants + c -> n_i_frame_variants ; i ++ ) {
struct variant_t * v = i < c -> n_variants ? c -> variants [ i ] : c -> i_frame_variants [ i - c -> n_variants ];
AVProgram * program ;
program = av_new_program ( s , i );
if ( ! program )
goto fail ;
av_dict_set_int ( & program -> metadata , "variant_bitrate" , v -> bandwidth , 0 );
}
#else
/* Create a program for each variant */
for ( i = 0 ; i < c -> n_variants ; i ++ ) {
struct variant_t * v = c -> variants [ i ];
AVProgram * program ;
program = av_new_program ( s , i );
if ( ! program )
goto fail ;
av_dict_set_int ( & program -> metadata , "variant_bitrate" , v -> bandwidth , 0 );
}
#endif
/* Select the starting segments */
for ( i = 0 ; i < c -> n_playlists ; i ++ ) {
struct playlist * pls = c -> playlists [ i ];
if ( pls -> n_segments == 0 )
continue ;
pls -> cur_seq_no = select_cur_seq_no ( c , pls );
highest_cur_seq_no = FFMAX ( highest_cur_seq_no , pls -> cur_seq_no );
}
av_log ( NULL , AV_LOG_INFO , "highest_cur_seq_no:%d" , highest_cur_seq_no );
for ( i = 0 ; i < c -> n_playlists ; i ++ ) {
struct playlist * pls = c -> playlists [ i ];
/*
* If this is a live stream and this playlist looks like it is one segment
* behind, try to sync it up so that every substream starts at the same
* time position (so e.g. avformat_find_stream_info() will see packets from
* all active streams within the first few seconds). This is not very generic,
* though, as the sequence numbers are technically independent.
*/
if ( ! pls -> finished && pls -> cur_seq_no == highest_cur_seq_no - 1 &&
highest_cur_seq_no < pls -> start_seq_no + pls -> n_segments ) {
pls -> cur_seq_no = highest_cur_seq_no ;
}
av_log ( NULL , AV_LOG_INFO , "recheck, pls:%d, cur_seq_no:%d" , i , pls -> cur_seq_no );
}
// pre open url before before really read packet one by one;
if ( ! c -> read_packet_readed ) {
/*
* "read_packet_readed=true" means that in hls_read_header, the link to the cur segment has been created in advance.
* Doing this is not very good and may cause problems for ID3 timestamped raw audio streams.
*/
c -> read_packet_readed = 1 ;
av_log ( NULL , AV_LOG_INFO , "enter read_packet_readed. \n " );
for ( i = 0 ; i < c -> n_playlists ; i ++ ) {
struct playlist * pls = c -> playlists [ i ];
struct segment * seg = current_segment ( pls );
int ret_ = 0 ;
if ( seg && av_strstart ( seg -> url , "http" , NULL )) {
pls -> parent = s ;
pls -> parse_preloaded_segment_id3_requested = 1 ;
ret_ = update_init_section ( pls , seg );
av_log ( NULL , AV_LOG_INFO , "index:%d, open input ts, cur_seq_no:%d, start_seq_no:%d, cur-start diff:%d, n_segments:%d, duration:%lld, url:%s" , i , pls -> cur_seq_no , pls -> start_seq_no , pls -> cur_seq_no - pls -> start_seq_no , pls -> n_segments , seg -> duration , trimLeadingCharacter ( seg -> url ));
ret_ = open_input ( c , pls , seg , & pls -> input );
if ( ret_ < 0 ) {
if ( ff_check_interrupt ( c -> interrupt_callback )) {
return AVERROR_EXIT ;
}
av_log ( NULL , AV_LOG_WARNING , "Failed to open segment %d of playlist %d \n " ,
pls -> cur_seq_no ,
pls -> index );
}
}
}
}
/* Open the demuxer for each playlist */
for ( i = 0 ; i < c -> n_playlists ; i ++ ) {
struct playlist * pls = c -> playlists [ i ];
AVInputFormat * in_fmt = NULL ;
av_log ( NULL , AV_LOG_INFO , "Open the demuxer pls:%p, i:%d" , pls , i );
if ( ! ( pls -> ctx = avformat_alloc_context ())) {
ret = AVERROR ( ENOMEM );
goto fail ;
}
if ( pls -> n_segments == 0 )
continue ;
pls -> index = i ;
pls -> needed = 1 ;
pls -> parent = s ;
pls -> ctx -> interrupt_callback = s -> interrupt_callback ;
pls -> fetch_start = 0 ;
pls -> open_retrytime = 0 ;
pls -> read_retrytime = 0 ;
pls -> each_segment_read_done = 0 ;
pls -> reopen_playlist_requested = 0 ;
pls -> reopen_playlist_enable = 0 ;
pls -> switch_variant_requested = - 1 ;
/*
* If this is a live stream and this playlist looks like it is one segment
* behind, try to sync it up so that every substream starts at the same
* time position (so e.g. avformat_find_stream_info() will see packets from
* all active streams within the first few seconds). This is not very generic,
* though, as the sequence numbers are technically independent.
*/
if ( ! pls -> finished && pls -> cur_seq_no == highest_cur_seq_no - 1 &&
highest_cur_seq_no < pls -> start_seq_no + pls -> n_segments ) {
pls -> cur_seq_no = highest_cur_seq_no ;
}
pls -> read_buffer = av_malloc ( INITIAL_BUFFER_SIZE );
if ( ! pls -> read_buffer ) {
ret = AVERROR ( ENOMEM );
avformat_free_context ( pls -> ctx );
pls -> ctx = NULL ;
goto fail ;
}
ffio_init_context ( & pls -> pb , pls -> read_buffer , INITIAL_BUFFER_SIZE , 0 , pls ,
read_data , NULL , NULL );
//pls->pb.seekable = 0;
ret = av_probe_input_buffer ( & pls -> pb , & in_fmt , pls -> segments [ 0 ] -> url ,
NULL , 0 , 0 );
if ( ret < 0 ) {
/* Free the ctx - it isn't initialized properly at this point,
* so avformat_close_input shouldn't be called. If
* avformat_open_input fails below, it frees and zeros the
* context, so it doesn't need any special treatment like this. */
av_log ( NULL , AV_LOG_ERROR , "Error when loading first segment '%s' \n " , pls -> segments [ 0 ] -> url );
avformat_free_context ( pls -> ctx );
pls -> ctx = NULL ;
goto fail ;
}
pls -> ctx -> pb = & pls -> pb ;
pls -> ctx -> io_open = nested_io_open ;
pls -> ctx -> flags |= s -> flags & ~ AVFMT_FLAG_CUSTOM_IO ;
if ( c -> hls_switch_pmt )
pls -> ctx -> parent = ( void * ) pls ;
else
pls -> ctx -> parent = NULL ;
if (( ret = ff_copy_whiteblacklists ( pls -> ctx , s )) < 0 )
goto fail ;
AVDictionary * opts = NULL ;
av_dict_set_int ( & opts , "hls_live_mode" , ! pls -> finished , 0 );
av_dict_set_int ( & opts , "sidx_reference_count" , 0 , 0 );
ret = avformat_open_input ( & pls -> ctx , pls -> segments [ 0 ] -> url , in_fmt , & opts );
av_dict_free ( & opts );
if ( ret < 0 )
goto fail ;
if ( pls -> ctx -> iformat && pls -> ctx -> iformat -> name )
{
av_log ( NULL , AV_LOG_INFO , "pls->ctx->iformat->name:%s" , pls -> ctx -> iformat -> name );
if ( ! strcmp ( pls -> ctx -> iformat -> name , "mpegts" )) {
struct MpegTSContext * context = ( struct MpegTSContext * ) pls -> ctx -> priv_data ;
if ( context )
{
context -> mpegts_notify = c -> hls_notify ;
context -> session = c -> session ;
if ( c -> hls_switch_pmt )
context -> parent_notify = ( ffmpegts_notify_t ) mhls_notify ;
}
} else if ( ! strcmp ( pls -> ctx -> iformat -> name , "mov,mp4,m4a,3gp,3g2,mj2" )) {
//mp4
pls -> reopen_playlist_enable = 1 ;
av_log ( NULL , AV_LOG_INFO , "reopen_playlist_enable:%d, pls:%p, %s" , pls -> reopen_playlist_enable , pls , trimLeadingCharacter ( pls -> segments [ 0 ] -> url ));
}
}
if ( pls -> id3_deferred_extra && pls -> ctx -> nb_streams == 1 ) {
ff_id3v2_parse_apic ( pls -> ctx , & pls -> id3_deferred_extra );
avformat_queue_attached_pictures ( pls -> ctx );
ff_id3v2_parse_priv ( pls -> ctx , & pls -> id3_deferred_extra );
ff_id3v2_free_extra_meta ( & pls -> id3_deferred_extra );
pls -> id3_deferred_extra = NULL ;
}
if ( pls -> is_id3_timestamped == - 1 )
av_log ( NULL , AV_LOG_WARNING , "No expected HTTP requests have been made \n " );
/*
* For ID3 timestamped raw audio streams we need to detect the packet
* durations to calculate timestamps in fill_timing_for_id3_timestamped_stream(),
* but for other streams we can rely on our user calling avformat_find_stream_info()
* on us if they want to.
*/
if ( pls -> is_id3_timestamped ) {
ret = avformat_find_stream_info ( pls -> ctx , NULL );
if ( ret < 0 )
goto fail ;
}
pls -> has_noheader_flag = !! ( pls -> ctx -> ctx_flags & AVFMTCTX_NOHEADER );
/* Create new AVStreams for each stream in this playlist */
ret = update_streams_from_subdemuxer ( s , pls );
if ( ret < 0 )
goto fail ;
/*
* Copy any metadata from playlist to main streams, but do not set
* event flags.
*/
if ( pls -> n_main_streams )
av_dict_copy ( & pls -> main_streams [ 0 ] -> metadata , pls -> ctx -> metadata , 0 );
add_metadata_from_renditions ( s , pls , AVMEDIA_TYPE_AUDIO );
add_metadata_from_renditions ( s , pls , AVMEDIA_TYPE_VIDEO );
add_metadata_from_renditions ( s , pls , AVMEDIA_TYPE_SUBTITLE );
}
update_noheader_flag ( s );
//for index change when switch BW
c -> index_defaule_max = s -> nb_streams - 1 ;
int nb_streams = s -> nb_streams ;
for ( i = 0 ; i < nb_streams ; i ++ )
{
enum AVMediaType codec_type = s -> streams [ i ] -> codecpar -> codec_type ;
if ( codec_type == AVMEDIA_TYPE_AUDIO ) {
c -> index_defaule_audio = i ;
} else if ( codec_type == AVMEDIA_TYPE_SUBTITLE ) {
c -> index_defaule_sub = i ;
} else if ( codec_type == AVMEDIA_TYPE_VIDEO ) {
c -> index_defaule_video = i ;
}
}
int64_t cost_time = av_gettime_relative () - begin_time ;
av_log ( s , AV_LOG_INFO , "hls_read_header done, cost time: %.2f ms" , cost_time / 1e3 );
return 0 ;
fail :
//hls_close(s);
return ret ;
}
static int recheck_discard_flags ( AVFormatContext * s , int first )
{
HLSContext * c = s -> priv_data ;
int i , changed = 0 ;
int cur_needed ;
/* Check if any new streams are needed */
for ( i = 0 ; i < c -> n_playlists ; i ++ ) {
struct playlist * pls = c -> playlists [ i ];
cur_needed = playlist_needed ( c -> playlists [ i ]);
if ( cur_needed && ! pls -> needed ) {
pls -> needed = 1 ;
changed = 1 ;
pls -> cur_seq_no = select_cur_seq_no ( c , pls );
pls -> pb . eof_reached = 0 ;
if ( c -> cur_timestamp != AV_NOPTS_VALUE ) {
/* catch up */
pls -> seek_timestamp = c -> cur_timestamp ;
pls -> seek_flags = AVSEEK_FLAG_ANY ;
pls -> seek_stream_index = - 1 ;
}
av_log ( NULL , AV_LOG_INFO , "Now receiving playlist %d, segment %d \n " , i , pls -> cur_seq_no );
} else if ( first && ! cur_needed && pls -> needed ) {
if ( pls -> input )
ff_format_io_close ( pls -> parent , & pls -> input );
pls -> input_read_done = 0 ;
if ( pls -> input_next )
ff_format_io_close ( pls -> parent , & pls -> input_next );
if ( pls -> playlist_pb )
ff_format_io_close ( c -> ctx , & pls -> playlist_pb );
pls -> input_next_requested = 0 ;
pls -> needed = 0 ;
changed = 1 ;
av_log ( NULL , AV_LOG_INFO , "No longer receiving playlist %d \n " , i );
}
}
return changed ;
}
static void fill_timing_for_id3_timestamped_stream ( struct playlist * pls )
{
if ( pls -> id3_offset >= 0 ) {
pls -> pkt . dts = pls -> id3_mpegts_timestamp +
av_rescale_q ( pls -> id3_offset ,
pls -> ctx -> streams [ pls -> pkt . stream_index ] -> time_base ,
MPEG_TIME_BASE_Q );
if ( pls -> pkt . duration )
pls -> id3_offset += pls -> pkt . duration ;
else
pls -> id3_offset = - 1 ;
} else {
/* there have been packets with unknown duration
* since the last id3 tag, should not normally happen */
pls -> pkt . dts = AV_NOPTS_VALUE ;
}
if ( pls -> pkt . duration )
pls -> pkt . duration = av_rescale_q ( pls -> pkt . duration ,
pls -> ctx -> streams [ pls -> pkt . stream_index ] -> time_base ,
MPEG_TIME_BASE_Q );
pls -> pkt . pts = AV_NOPTS_VALUE ;
}
static AVRational get_timebase ( struct playlist * pls )
{
if ( pls -> is_id3_timestamped )
return MPEG_TIME_BASE_Q ;
return pls -> ctx -> streams [ pls -> pkt . stream_index ] -> time_base ;
}
static int64_t compare_ts_with_wrapdetect ( int64_t ts_a , struct playlist * pls_a ,
int64_t ts_b , struct playlist * pls_b )
{
int64_t scaled_ts_a = av_rescale_q ( ts_a , get_timebase ( pls_a ), MPEG_TIME_BASE_Q );
int64_t scaled_ts_b = av_rescale_q ( ts_b , get_timebase ( pls_b ), MPEG_TIME_BASE_Q );
return av_compare_mod ( scaled_ts_a , scaled_ts_b , 1LL << 33 );
}
static int hls_read_packet ( AVFormatContext * s , AVPacket * pkt )
{
HLSContext * c = s -> priv_data ;
int ret = AVERROR_EOF , i , minplaylist = - 1 , isEof = 0 ;
int switch_varrant = - 1 ;
if ( c -> hls_switch_pmt && ( s -> switching_param & ffchange_end ))
{
for ( int i = 0 ; i < c -> n_playlists ; i ++ )
{
if ( c -> playlists [ i ] -> ctx -> switching_param != ffchange_none )
{
c -> playlists [ i ] -> ctx -> switching_param = ffchange_none ;
av_log ( NULL , AV_LOG_INFO , "ctx=%p, set switching_param=%d" , c -> playlists [ i ] -> ctx , c -> playlists [ i ] -> ctx -> switching_param );
}
}
s -> switching_param = ffchange_none ;
}
recheck_discard_flags ( s , c -> first_packet );
c -> first_packet = 0 ;
/* get the smallest seq*/
int min_seq = - 1 ;
for ( i = 0 ; i < c -> n_playlists ; i ++ ) {
struct playlist * pls = c -> playlists [ i ];
if ( pls -> needed && pls -> cur_seq_no >= 0 ) {
min_seq = min_seq < 0 ? pls -> cur_seq_no : FFMIN ( min_seq , pls -> cur_seq_no );
}
}
for ( i = 0 ; i < c -> n_playlists ; i ++ ) {
struct playlist * pls = c -> playlists [ i ];
if ( pls -> finished && pls -> needed && pls -> n_segments > 0 && pls -> cur_seq_no >= pls -> n_segments - 1 ) {
isEof = 1 ; /* when cur_seq_no is last seq no and open fail, consider stream is eof */
}
/* Make sure we've got one buffered packet from each open playlist
* stream */
av_log ( NULL , AV_LOG_DEBUG , "min_seq:%d, pls->cur_seq_no:%d, pls:%p, i:%d" , min_seq , pls -> cur_seq_no , pls , i );
if ( pls -> needed && ! pls -> pkt . data && min_seq == pls -> cur_seq_no ) {
while ( 1 ) {
int64_t ts_diff ;
AVRational tb ;
if ( c -> enableRawData ) {
av_init_packet ( & pls -> pkt );
int size_packet = 188 * 100 ;
uint8_t * buf = malloc ( size_packet );
ret = avio_read ( & pls -> pb , buf , size_packet );
if ( ret > 0 ) {
av_new_packet ( & pls -> pkt , ret );
memcpy ( pls -> pkt . data , buf , ret );
ret = 0 ;
}
free ( buf );
} else {
ret = av_read_frame ( pls -> ctx , & pls -> pkt );
if ( ret < 0 ) {
if ( pls -> init_section_update_status == INIT_SEC_UPDATE_WAITING_REOPEN || pls -> reopen_playlist_requested ) {
av_log ( NULL , AV_LOG_INFO , "pls->init_section_update_status:%d,pls->reopen_playlist_requested:%d" , pls -> init_section_update_status , pls -> reopen_playlist_requested );
//always update, clear first
pls -> reopen_playlist_requested = 0 ;
pls -> each_segment_read_done = 0 ;
if ( pls -> switch_variant_requested != - 1 ) {
pls -> switch_variant_requested = - 1 ;
av_log ( NULL , AV_LOG_INFO , "enter update_status or reopen_playlist_requested, force switch_variant_requested=-1" );
}
//reopen_pls
ret = reopen_pls ( s , pls );
if ( ret < 0 )
return ret ;
ret = av_read_frame ( pls -> ctx , & pls -> pkt );
} else if ( pls -> switch_variant_requested != - 1 ) {
//always update, clear first
switch_varrant = pls -> switch_variant_requested ;
pls -> switch_variant_requested = - 1 ;
//only video playlist will enter switch_variant_requested, so clearing the reopen parameter is possible
pls -> reopen_playlist_requested = 0 ;
pls -> each_segment_read_done = 0 ;
//hls_bw_switch_varrant
#ifdef HLS_I_FRAME_MODE
if ( c -> n_variants > 1 && ( c -> quality == 2 || c -> quality == 3 ) && ! c -> i_frame_mode )
#else
if ( c -> n_variants > 1 && ( c -> quality == 2 || c -> quality == 3 ))
#endif
{
//make sure v is the main media
if ( i == 0 ) {
ret = hls_bw_switch_varrant ( s , switch_varrant );
if ( ret < 0 )
return ret ;
//update new pls
pls = c -> playlists [ 0 ];
}
}
ret = av_read_frame ( pls -> ctx , & pls -> pkt );
}
}
av_log ( 0 , AV_LOG_DEBUG , "pls->pkt.pts:%lld, ret:%d, c->first_timestamp:%lld, pls->init_section_update_status:%d, pls->reopen_playlist_requested:%d,"
"pls->index:%d, pls->pkt.stream_index:%d, codec_type:%d, ctx_codec_type:%d \n " ,
pls -> pkt . pts , ret , c -> first_timestamp , pls -> init_section_update_status , pls -> reopen_playlist_requested ,
pls -> index , pls -> pkt . stream_index , pls -> main_streams [ pls -> pkt . stream_index ] -> codecpar -> codec_type , pls -> ctx -> streams [ pls -> pkt . stream_index ] -> codecpar -> codec_type );
if ( c -> first_timestamp >= 0 && ret >= 0 && pls -> main_streams [ pls -> pkt . stream_index ] -> codecpar -> codec_type == AVMEDIA_TYPE_SUBTITLE ) {
int64_t p = av_rescale_q ( pls -> pkt . pts , get_timebase ( pls ), AV_TIME_BASE_Q );
struct segment * seg = current_segment ( pls );
if ( seg ) {
pls -> use_segment_timestamp = 1 ;
//update use_segment_timestamp_increment_step
int64_t step_us = 500 * 1000 ; //500ms
int64_t out_val = 0 ;
if ( pls -> input ) {
if ( av_opt_get_int ( pls -> ctx , "sidx_reference_count" , AV_OPT_SEARCH_CHILDREN , & out_val ) == 0 && out_val > 0 ) {
step_us = seg -> duration / out_val ;
}
}
pls -> use_segment_timestamp_increment_step = step_us ;
}
av_log ( 0 , AV_LOG_DEBUG , "first_timestamp=%lld, pls->index:%d, cur_seg timestamp_us_in_url:%lld, diff:%lld(s),"
"use_segment_timestamp:%d, step:%d, pls:%p, pls->url:%s, pkt.duration:%lld, seg->duration:%lld \n " ,
c -> first_timestamp , pls -> index , seg -> timestamp_us_in_url , ( FFABS ( p - seg -> timestamp_us_in_url ) / AV_TIME_BASE ),
pls -> use_segment_timestamp , pls -> use_segment_timestamp_increment_step , pls , trimLeadingCharacter ( pls -> url ), pls -> pkt . duration , seg -> duration );
}
if ( pls -> use_segment_timestamp && ret >= 0 ) {
struct segment * seg = current_segment ( pls );
if ( seg -> timestamp_us_in_url > 0 && seg -> timestamp_us_in_url_next > 0 ) {
int64_t old_timestamp = pls -> pkt . pts ;
int64_t timestamp_in_url = av_rescale_q ( seg -> timestamp_us_in_url_next , AV_TIME_BASE_Q , get_timebase ( pls ));
int64_t sub_diff = av_rescale ( timestamp_in_url - old_timestamp , get_timebase ( pls ). num , get_timebase ( pls ). den );
pls -> pkt . pts = timestamp_in_url ;
pls -> pkt . dts = timestamp_in_url ;
if ( c -> hls_optimize_segment_timestamp_enable ) {
if ( pls -> pkt . duration > 0 ) {
seg -> timestamp_us_in_url_next += av_rescale_q ( pls -> pkt . duration , get_timebase ( pls ), AV_TIME_BASE_Q );
} else {
seg -> timestamp_us_in_url_next += pls -> use_segment_timestamp_increment_step ;
}
seg -> timestamp_us_in_url_next = seg -> timestamp_us_in_url_next > seg -> timestamp_us_in_url + seg -> duration ? seg -> timestamp_us_in_url + seg -> duration : seg -> timestamp_us_in_url_next ;
}
av_log ( 0 , AV_LOG_DEBUG , "reconstructed_pts=%lld, old_timestamp:%lld, sub_diff:%lld(s), next_pts:%lld, pls->pkt.duration:%lld, timestamp_us_in_url:%lld, timebase[%d,%d] pls:%p, url:%s \n " ,
timestamp_in_url , old_timestamp , sub_diff , seg -> timestamp_us_in_url_next , av_rescale_q ( pls -> pkt . duration , get_timebase ( pls ), AV_TIME_BASE_Q ),
seg -> timestamp_us_in_url , get_timebase ( pls ). num , get_timebase ( pls ). den , pls , trimLeadingCharacter ( pls -> url ));
}
}
}
if ( ret < 0 ) {
av_log ( NULL , AV_LOG_ERROR , "av_read_frame ret:%d, cur_seq_no:%d, start_seq:%d, n_segments:%d, pls:%p, ctx:%p, url:%s" , ret , pls -> cur_seq_no , pls -> start_seq_no , pls -> n_segments , pls , pls -> ctx , trimLeadingCharacter ( pls -> url ));
if ( ! avio_feof ( & pls -> pb ) && ret != AVERROR_EOF ) {
return ret ;
}
reset_packet ( & pls -> pkt );
break ;
} else {
/* stream_index check prevents matching picture attachments etc. */
if ( pls -> is_id3_timestamped && pls -> pkt . stream_index == 0 ) {
/* audio elementary streams are id3 timestamped */
fill_timing_for_id3_timestamped_stream ( pls );
}
if ( c -> first_timestamp == AV_NOPTS_VALUE &&
pls -> pkt . dts != AV_NOPTS_VALUE )
c -> first_timestamp = av_rescale_q ( pls -> pkt . dts ,
get_timebase ( pls ), AV_TIME_BASE_Q );
}
if ( pls -> seek_timestamp == AV_NOPTS_VALUE )
break ;
if ( pls -> seek_stream_index < 0 ||
pls -> seek_stream_index == pls -> pkt . stream_index ) {
if ( pls -> pkt . dts == AV_NOPTS_VALUE ) {
pls -> seek_timestamp = AV_NOPTS_VALUE ;
break ;
}
tb = get_timebase ( pls );
ts_diff = av_rescale_rnd ( pls -> pkt . dts , AV_TIME_BASE ,
tb . den , AV_ROUND_DOWN ) -
pls -> seek_timestamp ;
if ( ts_diff >= 0 && ( pls -> seek_flags & AVSEEK_FLAG_ANY ||
pls -> pkt . flags & AV_PKT_FLAG_KEY )) {
pls -> seek_timestamp = AV_NOPTS_VALUE ;
break ;
}
}
av_packet_unref ( & pls -> pkt );
reset_packet ( & pls -> pkt );
}
}
/* Check if this stream has the packet with the lowest dts */
if ( pls -> pkt . data ) {
struct playlist * minpls = ( minplaylist < 0 ? NULL : c -> playlists [ minplaylist ]);
if ( minplaylist < 0 ) {
minplaylist = i ;
} else {
int64_t dts = pls -> pkt . dts ;
int64_t mindts = minpls -> pkt . dts ;
if ( dts == AV_NOPTS_VALUE ||
( mindts != AV_NOPTS_VALUE && compare_ts_with_wrapdetect ( dts , pls , mindts , minpls ) < 0 ))
minplaylist = i ;
}
}
}
/* If we got a packet, return it */
if ( minplaylist >= 0 ) {
struct playlist * pls = c -> playlists [ minplaylist ];
AVStream * ist ;
AVStream * st ;
ret = update_streams_from_subdemuxer ( s , pls );
if ( ret < 0 ) {
av_packet_unref ( & pls -> pkt );
reset_packet ( & pls -> pkt );
return ret ;
}
// If sub-demuxer reports updated metadata, copy it to the first stream
// and set its AVSTREAM_EVENT_FLAG_METADATA_UPDATED flag.
if ( pls -> ctx -> event_flags & AVFMT_EVENT_FLAG_METADATA_UPDATED ) {
if ( pls -> n_main_streams ) {
st = pls -> main_streams [ 0 ];
av_dict_copy ( & st -> metadata , pls -> ctx -> metadata , 0 );
st -> event_flags |= AVSTREAM_EVENT_FLAG_METADATA_UPDATED ;
}
pls -> ctx -> event_flags &= ~ AVFMT_EVENT_FLAG_METADATA_UPDATED ;
}
/* check if noheader flag has been cleared by the subdemuxer */
if ( pls -> has_noheader_flag && ! ( pls -> ctx -> ctx_flags & AVFMTCTX_NOHEADER )) {
pls -> has_noheader_flag = 0 ;
update_noheader_flag ( s );
}
if ( pls -> pkt . stream_index >= pls -> n_main_streams ) {
av_log ( NULL , AV_LOG_ERROR , "stream index inconsistency: index %d, %d main streams, %d subdemuxer streams \n " ,
pls -> pkt . stream_index , pls -> n_main_streams , pls -> ctx -> nb_streams );
av_packet_unref ( & pls -> pkt );
reset_packet ( & pls -> pkt );
return AVERROR_BUG ;
}
ist = pls -> ctx -> streams [ pls -> pkt . stream_index ];
st = pls -> main_streams [ pls -> pkt . stream_index ];
* pkt = pls -> pkt ;
pkt -> stream_index = st -> index ;
if ( pls -> init_section_update_notify == 1 ) {
pls -> init_section_update_notify = 0 ;
c -> update_pssh = 1 ;
} else {
c -> update_pssh = 0 ;
}
if ( pkt -> stream_index > c -> index_defaule_max ) {
if ( st -> codecpar -> codec_type == AVMEDIA_TYPE_VIDEO ) {
pkt -> stream_index = c -> index_defaule_video ;
} if ( st -> codecpar -> codec_type == AVMEDIA_TYPE_AUDIO ) {
pkt -> stream_index = c -> index_defaule_audio ;
} else if ( st -> codecpar -> codec_type == AVMEDIA_TYPE_SUBTITLE ) {
pkt -> stream_index = c -> index_defaule_sub ;
}
}
reset_packet ( & c -> playlists [ minplaylist ] -> pkt );
if ( pkt -> dts != AV_NOPTS_VALUE )
c -> cur_timestamp = av_rescale_q ( pkt -> dts ,
ist -> time_base ,
AV_TIME_BASE_Q );
/* There may be more situations where this would be useful, but this at least
* handles newly probed codecs properly (i.e. request_probe by mpegts). */
if ( ist -> codecpar -> codec_id != st -> codecpar -> codec_id ) {
ret = set_stream_info_from_input_stream ( st , pls , ist );
if ( ret < 0 ) {
av_packet_unref ( pkt );
return ret ;
}
}
return 0 ;
} else {
av_log ( NULL , AV_LOG_INFO , "minplaylist:%d, ret:%d, isEof:%d" , minplaylist , ret , isEof );
}
if ( isEof == 0 ) { /* if not read the last seg, do not return EOF */
return ret ;
}
return AVERROR_EOF ;
}
//copy form hls_read_packet
static int hls_read_one ( AVFormatContext * s , HLSContext * c , struct playlist * pls )
{
int ret = 0 ;
/* Make sure we've got one buffered packet from each open playlist
* stream */
if ( pls -> needed && ! pls -> pkt . data ) {
while ( 1 ) {
int64_t ts_diff ;
AVRational tb ;
ret = av_read_frame ( pls -> ctx , & pls -> pkt );
if ( ret < 0 && pls -> init_section_update_status == INIT_SEC_UPDATE_WAITING_REOPEN ) {
ret = reopen_pls ( s , pls );
if ( ret < 0 )
return ret ;
ret = av_read_frame ( pls -> ctx , & pls -> pkt );
}
if ( ret < 0 ) {
if ( ! avio_feof ( & pls -> pb ) && ret != AVERROR_EOF )
return ret ;
reset_packet ( & pls -> pkt );
break ;
} else {
/* stream_index check prevents matching picture attachments etc. */
if ( pls -> is_id3_timestamped && pls -> pkt . stream_index == 0 ) {
/* audio elementary streams are id3 timestamped */
fill_timing_for_id3_timestamped_stream ( pls );
}
if ( c -> first_timestamp == AV_NOPTS_VALUE &&
pls -> pkt . dts != AV_NOPTS_VALUE )
c -> first_timestamp = av_rescale_q ( pls -> pkt . dts ,
get_timebase ( pls ), AV_TIME_BASE_Q );
}
if ( pls -> seek_timestamp == AV_NOPTS_VALUE )
break ;
if ( pls -> seek_stream_index < 0 ||
pls -> seek_stream_index == pls -> pkt . stream_index ) {
if ( pls -> pkt . dts == AV_NOPTS_VALUE ) {
pls -> seek_timestamp = AV_NOPTS_VALUE ;
break ;
}
tb = get_timebase ( pls );
ts_diff = av_rescale_rnd ( pls -> pkt . dts , AV_TIME_BASE ,
tb . den , AV_ROUND_DOWN ) -
pls -> seek_timestamp ;
if ( ts_diff >= 0 && ( pls -> seek_flags & AVSEEK_FLAG_ANY ||
pls -> pkt . flags & AV_PKT_FLAG_KEY )) {
pls -> seek_timestamp = AV_NOPTS_VALUE ;
break ;
}
}
av_packet_unref ( & pls -> pkt );
reset_packet ( & pls -> pkt );
}
}
return ret ;
}
static int hls_read_seek ( AVFormatContext * s , int stream_index ,
int64_t timestamp , int flags )
{
HLSContext * c = s -> priv_data ;
struct playlist * seek_pls = NULL ;
int i , seq_no ;
int j ;
int stream_subdemuxer_index = 0 ;
int64_t first_timestamp , seek_timestamp , duration ;
if (( flags & AVSEEK_FLAG_BYTE ) || ( c -> ctx -> ctx_flags & AVFMTCTX_UNSEEKABLE ))
return AVERROR ( ENOSYS );
first_timestamp = c -> first_timestamp == AV_NOPTS_VALUE ?
0 : c -> first_timestamp ;
seek_timestamp = av_rescale_rnd ( timestamp , AV_TIME_BASE ,
s -> streams [ stream_index ] -> time_base . den ,
flags & AVSEEK_FLAG_BACKWARD ?
AV_ROUND_DOWN : AV_ROUND_UP );
duration = s -> duration == AV_NOPTS_VALUE ?
0 : s -> duration ;
if ( 0 < duration && duration < seek_timestamp - first_timestamp )
return AVERROR ( EIO );
if ( c -> n_variants > 0 )
{
//if had switch BW,
//the index may be changed
//find the new index
int in_same_variants = 0 ;
for ( i = 0 ; i < c -> n_playlists ; i ++ ) {
struct playlist * pls = c -> playlists [ i ];
for ( j = 0 ; j < pls -> n_main_streams ; j ++ ) {
if ( pls -> main_streams [ j ] -> index == stream_index )
{
in_same_variants = 1 ;
break ;
}
}
}
if ( ! in_same_variants )
{
//index had change
AVStream * st = s -> streams [ stream_index ];
enum AVMediaType avMediaType = st -> codecpar -> codec_type ;
for ( i = 0 ; i < c -> n_playlists ; i ++ ) {
struct playlist * pls = c -> playlists [ i ];
for ( j = 0 ; j < pls -> n_main_streams ; j ++ ) {
if ( pls -> main_streams [ j ] -> codecpar -> codec_type == avMediaType )
{
stream_index = pls -> main_streams [ j ] -> index ;
break ;
}
}
}
}
}
/* find the playlist with the specified stream */
for ( i = 0 ; i < c -> n_playlists ; i ++ ) {
struct playlist * pls = c -> playlists [ i ];
for ( j = 0 ; j < pls -> n_main_streams ; j ++ ) {
if ( pls -> main_streams [ j ] == s -> streams [ stream_index ]) {
seek_pls = pls ;
stream_subdemuxer_index = j ;
break ;
}
}
}
/* check if the timestamp is valid for the playlist with the
* specified stream index */
if ( ! seek_pls )
return AVERROR ( EIO );
/* because seq_no is change, so donot care ret */
find_timestamp_in_playlist ( c , seek_pls , seek_timestamp , & seq_no );
av_log ( NULL , AV_LOG_INFO , "seek, new seq:%d, old seq:%d, n_segments:%d, seek_timestamp:%" PRId64 ", fastseek:%d \n " ,
seq_no , seek_pls -> cur_seq_no , seek_pls -> n_segments , seek_timestamp , c -> vod_fastseek );
if ( seq_no == seek_pls -> n_segments - 1 && ! c -> vod_fastseek )
{
//when seek to end,
//play some pic
//fastseek play from first pkt, so not need this
int64_t old_seek_timestamp = seek_timestamp ;
seek_timestamp -= seek_pls -> segments [ seek_pls -> n_segments - 1 ] -> duration ;
av_log ( NULL , AV_LOG_INFO , "new time:%" PRId64 ", old time:%" PRId64 " \n " , seek_timestamp , old_seek_timestamp );
}
/* set segment now so we do not need to search again below */
seek_pls -> cur_seq_no = seq_no ;
seek_pls -> seek_stream_index = stream_subdemuxer_index ;
for ( i = 0 ; i < c -> n_playlists ; i ++ ) {
/* Reset reading */
struct playlist * pls = c -> playlists [ i ];
if ( pls -> input )
ff_format_io_close ( pls -> parent , & pls -> input );
pls -> input_read_done = 0 ;
pls -> fetch_start = 0 ;
pls -> open_retrytime = 0 ;
pls -> read_retrytime = 0 ;
if ( pls -> input_next )
ff_format_io_close ( pls -> parent , & pls -> input_next );
if ( pls -> playlist_pb )
ff_format_io_close ( c -> ctx , & pls -> playlist_pb );
pls -> input_next_requested = 0 ;
av_packet_unref ( & pls -> pkt );
reset_packet ( & pls -> pkt );
pls -> pb . eof_reached = 0 ;
/* Clear any buffered data */
pls -> pb . buf_end = pls -> pb . buf_ptr = pls -> pb . buffer ;
/* Reset the pos, to let the mpegts demuxer know we've seeked. */
pls -> pb . pos = 0 ;
/* Flush the packet queue of the subdemuxer. */
ff_read_frame_flush ( pls -> ctx );
pls -> seek_timestamp = seek_timestamp ;
pls -> seek_flags = flags ;
if ( pls != seek_pls ) {
/* set closest segment seq_no for playlists not handled above */
find_timestamp_in_playlist ( c , pls , seek_timestamp , & pls -> cur_seq_no );
/* seek the playlist to the given position without taking
* keyframes into account since this playlist does not have the
* specified stream where we should look for the keyframes */
pls -> seek_stream_index = - 1 ;
pls -> seek_flags |= AVSEEK_FLAG_ANY ;
}
reopen_pls ( s , pls );
}
c -> cur_timestamp = seek_timestamp ;
if ( c -> vod_fastseek )
{
int ret ;
//read from first pkt
seek_pls -> seek_timestamp = 0 ;
ret = hls_read_one ( s , c , seek_pls );
if ( ret >= 0 )
{
seek_timestamp = av_rescale_rnd ( seek_pls -> pkt . dts , AV_TIME_BASE ,
seek_pls -> main_streams [ stream_subdemuxer_index ] -> time_base . den ,
flags & AVSEEK_FLAG_BACKWARD ?
AV_ROUND_DOWN : AV_ROUND_UP );
c -> cur_timestamp = seek_timestamp ;
for ( i = 0 ; i < c -> n_playlists ; i ++ ) {
struct playlist * pls = c -> playlists [ i ];
if ( pls == seek_pls )
continue ;
pls -> seek_timestamp = 0 ;
pls -> seek_flags = flags ;
find_timestamp_in_playlist ( c , pls , seek_timestamp , & pls -> cur_seq_no );
pls -> seek_stream_index = - 1 ;
pls -> seek_flags |= AVSEEK_FLAG_ANY ;
}
}
}
av_log ( NULL , AV_LOG_INFO , "seek end" );
return 0 ;
}
int hls_read_shift ( AVFormatContext * s , char * shift_url , int varrant_used )
{
HLSContext * c = s -> priv_data ;
int i ;
for ( i = 0 ; i < c -> n_playlists ; i ++ ) {
/* Reset reading */
struct playlist * pls = c -> playlists [ i ];
if ( pls -> input )
ff_format_io_close ( pls -> parent , & pls -> input );
pls -> input_read_done = 0 ;
pls -> fetch_start = 0 ;
pls -> open_retrytime = 0 ;
pls -> read_retrytime = 0 ;
if ( pls -> input_next )
ff_format_io_close ( pls -> parent , & pls -> input_next );
if ( pls -> playlist_pb )
ff_format_io_close ( c -> ctx , & pls -> playlist_pb );
pls -> input_next_requested = 0 ;
av_packet_unref ( & pls -> pkt );
reset_packet ( & pls -> pkt );
pls -> pb . eof_reached = 0 ;
/* Clear any buffered data */
pls -> pb . buf_end = pls -> pb . buf_ptr = pls -> pb . buffer ;
/* Reset the pos, to let the mpegts demuxer know we've seeked. */
pls -> pb . pos = 0 ;
/* Flush the packet queue of the subdemuxer. */
ff_read_frame_flush ( pls -> ctx );
pls -> seek_flags = 0 ;
}
struct variant_t * variant = c -> variants [ varrant_used ];
int ret = - 1 ;
for ( i = 0 ; i < variant -> n_playlists ; i ++ )
{
struct playlist * seek_pls = variant -> playlists [ i ];
if ( seek_pls && seek_pls -> timeshif_url )
{
av_freep ( & seek_pls -> timeshif_url );
}
ret = parse_playlist ( c , shift_url , seek_pls , NULL );
if ( ret >= 0 )
{
seek_pls -> cur_seq_no = seek_pls -> start_seq_no ;
memset ( seek_pls -> url , 0 , MAX_URL_SIZE );
strcpy ( seek_pls -> url , shift_url );
} else {
av_log ( NULL , AV_LOG_ERROR , "hls_read_shift err :%d" , ret );
break ;
}
}
return ret ;
}
static int hls_probe ( AVProbeData * p )
{
/* Require #EXTM3U at the start, and either one of the ones below
* somewhere for a proper match. */
if ( strncmp ( p -> buf , "#EXTM3U" , 7 ))
return 0 ;
if ( strstr ( p -> buf , "#EXT-X-STREAM-INF:" ) ||
strstr ( p -> buf , "#EXT-X-TARGETDURATION:" ) ||
strstr ( p -> buf , "#EXT-X-MEDIA-SEQUENCE:" ))
return AVPROBE_SCORE_MAX ;
return 0 ;
}
int64_t time_to_refresh ( int64_t reload_interval , int64_t target_duration ,
int64_t last_load_time , int64_t last_live_change_time , HLS_LIVE_refresh_STATE * live_change_state )
{
int64_t now_time = av_gettime_relative ();
{
//++ is not allow for enum, so just use number
int64_t diff_load_time = now_time - last_load_time ;
switch ( * live_change_state ) {
case HLS_LIVE_refreshed :
//the m3u8 had change when state = 0,
//MIN(target_duration, reload_interval, 9s)
break ;
//the follow is no change
//so need wait 1,1,1,2,5,10,10......
case HLS_LIVE_norefresh_1s_1 :
case HLS_LIVE_norefresh_1s_2 :
case HLS_LIVE_norefresh_1s_3 :
if ( diff_load_time > 1 * 1000 * 1000ll )
{
av_log ( NULL , AV_LOG_INFO , "state:%d, diff_load_time:%" PRId64 " > 1s" ,
* live_change_state , diff_load_time );
( * live_change_state ) ++ ;
return diff_load_time ;
}
else
return 0 ;
case HLS_LIVE_norefresh_2s :
if ( diff_load_time > 2 * 1000 * 1000ll )
{
av_log ( NULL , AV_LOG_INFO , "state:%d, diff_load_time:%" PRId64 " > 2s" ,
* live_change_state , diff_load_time );
( * live_change_state ) ++ ;
return diff_load_time ;
}
else
return 0 ;
case HLS_LIVE_norefresh_5s :
if ( diff_load_time > 5 * 1000 * 1000ll )
{
av_log ( NULL , AV_LOG_INFO , "state:%d, diff_load_time:%" PRId64 " > 5s" ,
* live_change_state , diff_load_time );
( * live_change_state ) ++ ;
return diff_load_time ;
}
else
return 0 ;
case HLS_LIVE_norefresh_10s :
default :
{
if ( diff_load_time > 10 * 1000 * 1000ll )
{
av_log ( NULL , AV_LOG_INFO , "state:%d, diff_load_time:%" PRId64 " > 10s" ,
* live_change_state , diff_load_time );
return diff_load_time ;
}
else
return 0 ;
}
}
}
//only live_change_state == 0 can reach here
//for m3u8 change case
//MIN(target_duration, reload_interval, 9s)
int64_t diff_time = now_time - last_load_time ;
if ( diff_time >= reload_interval || diff_time >= 9 * 1000000ll || diff_time >= target_duration )
{
av_log ( NULL , AV_LOG_INFO , "no change state:%d, diff_time:%" PRId64 "" ,
* live_change_state , diff_time );
return diff_time ;
} else {
return 0 ;
}
}
int64_t time_to_shift ( HLSContext * c , int varrant_used )
{
struct variant_t * variant = c -> variants [ varrant_used ];
struct playlist * pls = variant -> playlists [ 0 ];
int n_back = 0 ;
int seg_time = 0 ;
//the read_data fun not yet read and close the playlist_pb
//so wait
int n = variant -> n_playlists ;
int i = 0 ;
for ( i = 0 ; i < n ; i ++ )
{
if ( variant -> playlists [ i ] -> playlist_pb )
return 0 ;
}
//20s before play all seg is the n_back
int n_segments = pls -> n_segments ;
for (; n_segments > 0 ; n_segments -- )
{
seg_time += pls -> segments [ n_segments - 1 ] -> duration ;
if ( seg_time >= 20 )
{
break ;
}
n_back ++ ;
}
if ( pls -> cur_seq_no >= pls -> start_seq_no + pls -> n_segments - n_back )
{
//time to shift when open the cur_seq_no
return ( int64_t ) pls -> cur_seq_no ;
}
return 0 ;
}
void free_avio_list ( AVFormatContext * s , AVIOContext *** avio , int * n_avio , int need_close )
{
int i ;
if ( s == NULL || avio == NULL || * avio == NULL || ** avio == NULL
|| n_avio == NULL || * n_avio == 0 )
return ;
for ( i = 0 ; i < * n_avio ; i ++ ) {
struct AVIOContext * var = ( * avio )[ i ];
if ( var )
{
if ( need_close )
ff_format_io_close ( s , & var );
}
}
av_freep ( avio );
* n_avio = 0 ;
}
int open_varant ( AVFormatContext * s , const char * shift_url , int varrant_used , struct rendition * r , int n_renditions ,
AVIOContext *** playlist_pb , int * n_pls )
{
int ret = - 1 ;
int need_close = 0 ;
if ( r == NULL || n_renditions == 0 )
{
HLSContext * c = ( HLSContext * ) s -> priv_data ;
AVDictionary * opts = NULL ;
//get_m3u8_opts(c, &opts);
if ( c -> read_data_error ) {
//not open_varant
return AVERROR_EXIT ;
}
av_dict_set_int ( & opts , "http_wait_eof" , 1 , 0 );
//open all media
struct variant_t * v = c -> variants [ varrant_used ];
int n = v -> n_playlists ;
int i = 0 ;
av_log ( NULL , AV_LOG_VERBOSE , "[%d]in open_varant n:%d" , __LINE__ , n );
for ( i = 0 ; i < n ; i ++ )
{
AVIOContext * in ;
const char * url ;
if ( shift_url )
{
url = shift_url ;
} else {
if ( v -> playlists [ i ] -> redirect_url )
url = v -> playlists [ i ] -> redirect_url ;
else
url = v -> playlists [ i ] -> url ;
}
av_log ( NULL , AV_LOG_VERBOSE , "[%d]start open_varant url:%s" , __LINE__ , trimLeadingCharacter ( url ));
get_m3u8_opts ( c , & opts );
ret = amlio_open_whitelist ( & in , url , AVIO_FLAG_READ , & s -> interrupt_callback , & opts ,
s -> protocol_whitelist , s -> protocol_blacklist );
if ( ret >= 0 )
{
//will use the buf in http proto
//to avoid copy m3u8 data one more time
//so close the buf in this AVIOContext
in -> direct = 1 ;
if ( shift_url )
{
if ( v -> playlists [ i ] -> timeshif_url )
{
av_freep ( & v -> playlists [ i ] -> timeshif_url );
}
v -> playlists [ i ] -> timeshif_url = av_strdup ( shift_url );
}
dynarray_add ( playlist_pb , n_pls , in );
} else {
if ( check_net_phy_conn_status () == 0 )
{
av_log ( NULL , AV_LOG_WARNING , "hls net down" );
set_dns_net_down ();
} else {
av_freep ( & v -> playlists [ i ] -> redirect_url );
}
need_close = 1 ;
break ;
}
}
av_log ( NULL , AV_LOG_VERBOSE , "[%d]out open_varant" , __LINE__ );
av_dict_free ( & opts );
} else {
//TODO:
//open media from rendition
}
//if one err, should close other success
if ( need_close )
{
free_avio_list ( s , playlist_pb , n_pls , 1 );
}
return ret ;
}
static int64_t get_cur_us ( void )
{
struct timeval tv ;
gettimeofday ( & tv , NULL );
int64_t time = ( int64_t ) tv . tv_sec * 1000 * 1000 + tv . tv_usec ;
return time ;
}
#define TIMESHIFT_URL_STARTTIME "%Y%m%dT%H%M%S.00Z"
#define TIMESHIFT_URL_TAG "&starttime=%s" //new standard no need token
static char * get_formatted_time ( int64_t timeUs )
{
struct tm * tm_now ;
int64_t lst = timeUs ;
lst = lst / 1000000 ;
char strTime [ 100 ];
time_t timer = lst ;
tm_now = localtime ( & timer );
strftime ( strTime , sizeof ( strTime ), TIMESHIFT_URL_STARTTIME , tm_now );
int slen = strlen ( strTime ) + 1 ;
char * p = ( char * ) av_mallocz ( slen );
* ( p + slen - 1 ) = '\0' ;
strcpy ( p , strTime );
return p ;
}
int make_timeshift_url ( char * url , char * shift_url , int64_t us , int varrant_used )
{
char tmp_url [ MAX_URL_SIZE ];
snprintf ( tmp_url , MAX_URL_SIZE , "%s" , url );
char * tmp = get_formatted_time ( us );
char * start = strstr ( tmp_url , "&starttime=" );
if ( NULL == start )
{
snprintf ( tmp_url + strlen ( tmp_url ), MAX_URL_SIZE - strlen ( tmp_url ), TIMESHIFT_URL_TAG , tmp );
av_log ( NULL , AV_LOG_INFO , "no starttime, tmp_url:%s" , tmp_url );
}
else
{
strncpy ( start + 11 , tmp , strlen ( tmp ));
}
av_freep ( & tmp );
tmp = strstr ( tmp_url , "livemode=1" );
if ( NULL != tmp )
{
* ( tmp + 9 ) = '2' ;
}
av_log ( NULL , AV_LOG_INFO , "last, tmp_url:%s" , tmp_url );
strncpy ( shift_url , tmp_url , MAX_URL_SIZE );
//av_freep(&tmp_url);
return 0 ;
}
#ifdef HLS_I_FRAME_MODE
static int frames_in_ff_fb ( int scale ) {
int factor = ( scale >= 0 ? scale : - scale );
int ret = 1 ;
switch ( factor ) {
case I_FRAME_FF_FB_STEP_2X :
ret = 1 ;
break ;
case I_FRAME_FF_FB_STEP_4X :
ret = 2 ;
break ;
case I_FRAME_FF_FB_STEP_8X :
ret = 4 ;
break ;
case I_FRAME_FF_FB_STEP_16X :
ret = 6 ;
break ;
case I_FRAME_FF_FB_STEP_32X :
ret = 8 ;
break ;
case I_FRAME_FF_FB_STEP_64X :
ret = 10 ;
break ;
default :
ret = 1 ;
break ;
}
return ret ;
}
static int i_frame_find_drop_interval ( struct playlist * pls , int scale , int I_total_number )
{
AVFormatContext * s = pls -> parent ;
int drop_interval = 0 ;
int need_number = 0 ;
int total_in_speed = 0 ;
int drop_number = 0 ;
av_log ( NULL , AV_LOG_INFO , "scale:%d,I_total_number:%d \n " , scale , I_total_number );
need_number = frames_in_ff_fb ( scale );
// if (s->duration != 0) {
// total_in_speed = scale * I_total_number/(s->duration/1000000);
// av_log(NULL, AV_LOG_INFO,"total_in_speed:%d,scale:%d,I_total_number:%d,durationUs:%lld\n",
// total_in_speed, scale, I_total_number, s->duration);
// }
// drop_number = total_in_speed - need_number;
// if (drop_number > 0) {
// if (total_in_speed % drop_number == 0)
// drop_interval = total_in_speed/drop_number;
// else
// drop_interval = (total_in_speed + drop_number)/drop_number;
// av_log(NULL, AV_LOG_INFO,"drop_interval= %d,drop_number:%d\n",
// drop_interval,drop_number);
// }
return need_number ;
}
static void judge_next_segment ( struct playlist * pls )
{
AVFormatContext * s = pls -> parent ;
HLSContext * c = pls -> parent -> priv_data ;
int scale = c -> i_frame_ff_fb_scale ;
if ( c -> i_frame_mode ) {
int symbol = ( scale >= 0 ? 1 : - 1 );
int I_total_number = pls -> n_segments ;
av_log ( NULL , AV_LOG_INFO , "old cur seq no is %d \n " , pls -> cur_seq_no );
pls -> cur_seq_no += ( symbol * i_frame_find_drop_interval ( pls , scale , I_total_number ));
av_log ( NULL , AV_LOG_INFO , "new cur seq no is %d \n " , pls -> cur_seq_no );
} else {
pls -> cur_seq_no ++ ;
}
}
static struct variant_t * new_i_frame_variant ( HLSContext * c , struct i_frame_variant_info * info , const char * base )
{
struct variant_t * var ;
struct playlist * pls ;
pls = new_playlist ( c , info -> uri , base );
if ( ! pls )
return NULL ;
var = av_mallocz ( sizeof ( struct variant_t ));
if ( ! var )
return NULL ;
if ( info ) {
var -> bandwidth = atoi ( info -> bandwidth );
strcpy ( var -> audio_group , info -> audio );
strcpy ( var -> video_group , info -> video );
strcpy ( var -> subtitles_group , info -> subtitles );
}
dynarray_add ( & c -> i_frame_variants , & c -> n_i_frame_variants , var );
dynarray_add ( & var -> playlists , & var -> n_playlists , pls );
return var ;
}
static void handle_i_frame_variant_args ( struct i_frame_variant_info * info , const char * key ,
int key_len , char ** dest , int * dest_len )
{
if ( ! strncmp ( key , "BANDWIDTH=" , key_len )) {
* dest = info -> bandwidth ;
* dest_len = sizeof ( info -> bandwidth );
} else if ( ! strncmp ( key , "AUDIO=" , key_len )) {
* dest = info -> audio ;
* dest_len = sizeof ( info -> audio );
} else if ( ! strncmp ( key , "VIDEO=" , key_len )) {
* dest = info -> video ;
* dest_len = sizeof ( info -> video );
} else if ( ! strncmp ( key , "SUBTITLES=" , key_len )) {
* dest = info -> subtitles ;
* dest_len = sizeof ( info -> subtitles );
}
else if ( ! strncmp ( key , "PROGRAM-ID=" , key_len )) {
* dest = info -> program_id ;
* dest_len = sizeof ( info -> program_id );
}
else if ( ! strncmp ( key , "URI=" , key_len )) {
* dest = info -> uri ;
* dest_len = sizeof ( info -> uri );
}
}
int hls_switch_I_frame_back_to_normal_variant ( AVFormatContext * s , int varrant_used )
{
HLSContext * c = s -> priv_data ;
int ret = - 1 ;
struct variant_t * variant = NULL ;
struct playlist * pls = NULL ;
av_log ( NULL , AV_LOG_INFO , "LNTES----------->enter hls_switch_I_frame_back_to_normal_variant \n " );
if ( c -> i_frame_mode == 0 || c -> n_variants <= 0 || c -> n_variants < varrant_used )
{
av_log ( NULL , AV_LOG_ERROR , "hls_switch_variant err, i_frame_mode:%d, n_variants:%d, set value:%d \n " ,
c -> i_frame_mode , c -> n_variants , varrant_used );
return - 1 ;
}
int i ;
int varrant_used_last = c -> varrant_used ;
int cur_seq_no = c -> i_frame_variants [ varrant_used_last ] -> playlists [ 0 ] -> cur_seq_no ;
c -> varrant_used = varrant_used ;
variant = c -> variants [ c -> varrant_used ];
//close main input
{
int playlists_num = c -> n_playlists ;
av_log ( NULL , AV_LOG_ERROR , "LNTES----------->enter hls_switch_to_i_frame_variant varrant_used_last:%d, playlists_num:%d \n " , varrant_used_last , playlists_num );
for ( int i = 0 ; i < playlists_num ; i ++ ) {
reset_playlist ( c , c -> playlists [ i ]);
}
}
av_log ( NULL , AV_LOG_INFO , "LNTES----------->enter close varrant_used_last[%d] end \n " , varrant_used_last );
//clear playlists
for ( i = 0 ; i < c -> n_playlists_all ; i ++ )
{
c -> playlists [ i ] = NULL ;
}
//set new value
c -> n_playlists = variant -> n_playlists ;
for ( i = 0 ; i < variant -> n_playlists ; i ++ )
{
c -> playlists [ i ] = variant -> playlists [ i ];
}
//parse_playlist
for ( i = 0 ; i < c -> n_playlists ; i ++ )
{
struct playlist * pls = c -> playlists [ i ];
av_log ( NULL , AV_LOG_INFO , "LNTES----------->enter parse_playlist %s begin \n " , pls -> url );
if (( ret = parse_playlist ( c , pls -> url , pls , NULL )) < 0 )
goto fail ;
av_log ( NULL , AV_LOG_INFO , "LNTES----------->enter parse_playlist %s end \n " , pls -> url );
}
/* Select the starting segments */
for ( i = 0 ; i < c -> n_playlists ; i ++ )
{
struct playlist * pls = c -> playlists [ i ];
find_timestamp_in_playlist ( c , pls , c -> cur_timestamp , & pls -> cur_seq_no );
av_log ( NULL , AV_LOG_INFO , "LNTES----------->pls->cur_seq_no %d" , pls -> cur_seq_no );
pls -> needed = 1 ;
}
/* Open the demuxer for each playlist */
for ( i = 0 ; i < c -> n_playlists ; i ++ ) {
struct playlist * pls = c -> playlists [ i ];
AVInputFormat * in_fmt = NULL ;
if ( pls -> ctx )
{
continue ;
}
if ( ! ( pls -> ctx = avformat_alloc_context ())) {
ret = AVERROR ( ENOMEM );
goto fail ;
}
if ( pls -> n_segments == 0 )
continue ;
pls -> index = i ;
pls -> needed = 1 ;
pls -> parent = s ;
pls -> ctx -> interrupt_callback = s -> interrupt_callback ;
pls -> switch_variant_requested = - 1 ;
pls -> read_buffer = av_malloc ( INITIAL_BUFFER_SIZE );
if ( ! pls -> read_buffer ) {
ret = AVERROR ( ENOMEM );
avformat_free_context ( pls -> ctx );
pls -> ctx = NULL ;
goto fail ;
}
ffio_init_context ( & pls -> pb , pls -> read_buffer , INITIAL_BUFFER_SIZE , 0 , pls ,
read_data , NULL , NULL );
//pls->pb.seekable = 0;
ret = av_probe_input_buffer ( & pls -> pb , & in_fmt , pls -> segments [ 0 ] -> url ,
NULL , 0 , 0 );
if ( ret < 0 ) {
/* Free the ctx - it isn't initialized properly at this point,
* so avformat_close_input shouldn't be called. If
* avformat_open_input fails below, it frees and zeros the
* context, so it doesn't need any special treatment like this. */
av_log ( NULL , AV_LOG_ERROR , "Error when loading first segment '%s' \n " , pls -> segments [ 0 ] -> url );
avformat_free_context ( pls -> ctx );
pls -> ctx = NULL ;
goto fail ;
}
pls -> ctx -> pb = & pls -> pb ;
pls -> ctx -> io_open = nested_io_open ;
pls -> ctx -> flags |= s -> flags & ~ AVFMT_FLAG_CUSTOM_IO ;
if (( ret = ff_copy_whiteblacklists ( pls -> ctx , s )) < 0 )
goto fail ;
AVDictionary * opts = NULL ;
av_dict_set_int ( & opts , "hls_live_mode" , ! pls -> finished , 0 );
av_dict_set_int ( & opts , "sidx_reference_count" , 0 , 0 );
ret = avformat_open_input ( & pls -> ctx , pls -> segments [ 0 ] -> url , in_fmt , & opts );
av_dict_free ( & opts );
if ( ret < 0 )
goto fail ;
if ( pls -> id3_deferred_extra && pls -> ctx -> nb_streams == 1 ) {
ff_id3v2_parse_apic ( pls -> ctx , & pls -> id3_deferred_extra );
avformat_queue_attached_pictures ( pls -> ctx );
ff_id3v2_parse_priv ( pls -> ctx , & pls -> id3_deferred_extra );
ff_id3v2_free_extra_meta ( & pls -> id3_deferred_extra );
pls -> id3_deferred_extra = NULL ;
}
if ( pls -> is_id3_timestamped == - 1 )
av_log ( NULL , AV_LOG_WARNING , "No expected HTTP requests have been made \n " );
/*
* For ID3 timestamped raw audio streams we need to detect the packet
* durations to calculate timestamps in fill_timing_for_id3_timestamped_stream(),
* but for other streams we can rely on our user calling avformat_find_stream_info()
* on us if they want to.
*/
if ( pls -> is_id3_timestamped ) {
ret = avformat_find_stream_info ( pls -> ctx , NULL );
if ( ret < 0 )
goto fail ;
}
pls -> has_noheader_flag = !! ( pls -> ctx -> ctx_flags & AVFMTCTX_NOHEADER );
/* Create new AVStreams for each stream in this playlist */
av_log ( NULL , AV_LOG_INFO , "-------->update_streams_from_subdemuxer \n " );
ret = update_streams_from_subdemuxer ( s , pls );
if ( ret < 0 )
goto fail ;
/*
* Copy any metadata from playlist to main streams, but do not set
* event flags.
*/
if ( pls -> n_main_streams )
av_dict_copy ( & pls -> main_streams [ 0 ] -> metadata , pls -> ctx -> metadata , 0 );
add_metadata_from_renditions ( s , pls , AVMEDIA_TYPE_AUDIO );
add_metadata_from_renditions ( s , pls , AVMEDIA_TYPE_VIDEO );
add_metadata_from_renditions ( s , pls , AVMEDIA_TYPE_SUBTITLE );
}
update_noheader_flag ( s );
return 0 ;
fail :
av_log ( NULL , AV_LOG_ERROR , "hls_switch_I_frame_back_to_normal_variant err,ret:%d \n " , ret );
c -> varrant_used = varrant_used_last ;
variant = c -> variants [ c -> varrant_used ];
for ( i = 0 ; i < c -> n_playlists_all ; i ++ )
{
c -> playlists [ i ] = NULL ;
}
c -> n_playlists = variant -> n_playlists ;
for ( i = 0 ; i < variant -> n_playlists ; i ++ )
{
c -> playlists [ i ] = variant -> playlists [ i ];
}
return ret ;
}
static int reset_playlist ( HLSContext * c , struct playlist * pls )
{
if ( NULL == pls || NULL == c || NULL == c -> ctx ) {
av_log ( NULL , AV_LOG_ERROR , "bad param, pls is NULL \n " );
return AVERROR ( EINVAL );
}
/* Reset reading */
if ( pls -> input )
ff_format_io_close ( pls -> parent , & pls -> input );
pls -> input_read_done = 0 ;
pls -> open_retrytime = 0 ;
pls -> read_retrytime = 0 ;
if ( pls -> input_next )
ff_format_io_close ( pls -> parent , & pls -> input_next );
if ( pls -> playlist_pb )
ff_format_io_close ( c -> ctx , & pls -> playlist_pb );
pls -> input_next_requested = 0 ;
av_packet_unref ( & pls -> pkt );
reset_packet ( & pls -> pkt );
pls -> pb . eof_reached = 0 ;
/* Clear any buffered data */
pls -> pb . buf_end = pls -> pb . buf_ptr = pls -> pb . buffer ;
/* Reset the pos, to let the mpegts demuxer know we've seeked. */
pls -> pb . pos = 0 ;
/* Flush the packet queue of the subdemuxer. */
ff_read_frame_flush ( pls -> ctx );
pls -> seek_flags = 0 ;
pls -> needed = 0 ;
return 0 ;
}
int hls_switch_to_i_frame_variant ( AVFormatContext * s , int varrant_used )
{
HLSContext * c = s -> priv_data ;
int ret = - 1 ;
struct variant_t * variant = NULL ;
if ( c -> n_i_frame_variants < 1 || varrant_used > c -> n_i_frame_variants - 1 )
{
av_log ( NULL , AV_LOG_ERROR , "hls_switch_variant err, n_i_frame_variants:%d, set value:%d" ,
c -> n_i_frame_variants , varrant_used );
return - 1 ;
}
int i ;
int varrant_used_last = c -> varrant_used ;
int cur_seq_no = c -> variants [ varrant_used_last ] -> playlists [ 0 ] -> cur_seq_no ;
c -> varrant_used = varrant_used ;
variant = c -> i_frame_variants [ varrant_used ];
//close main input
{
int playlists_num = c -> n_playlists ;
av_log ( NULL , AV_LOG_ERROR , "LNTES----------->enter hls_switch_to_i_frame_variant varrant_used_last:%d, playlists_num:%d \n " , varrant_used_last , playlists_num );
for ( int i = 0 ; i < playlists_num ; i ++ )
{
reset_playlist ( c , c -> playlists [ i ]);
}
}
//clear playlists
for ( i = 0 ; i < c -> n_playlists_all ; i ++ )
{
c -> playlists [ i ] = NULL ;
}
//set new value
c -> n_playlists = variant -> n_playlists ;
for ( i = 0 ; i < variant -> n_playlists ; i ++ )
{
c -> playlists [ i ] = variant -> playlists [ i ];
}
//parse_playlist
for ( i = 0 ; i < c -> n_playlists ; i ++ )
{
struct playlist * pls = c -> playlists [ i ];
// struct playlist *pls = variant->playlists[0];
if (( ret = parse_playlist ( c , pls -> url , pls , NULL )) < 0 )
goto fail ;
}
/* Select the starting segments */
for ( i = 0 ; i < c -> n_playlists ; i ++ )
{
struct playlist * pls = c -> playlists [ i ];
// struct playlist *pls = variant->playlists[0];
// pls->cur_seq_no = cur_seq_no;
find_timestamp_in_playlist ( c , pls , c -> cur_timestamp , & pls -> cur_seq_no );
av_log ( NULL , AV_LOG_INFO , "LNTEST----------->pls->cur_seq_no %d" , pls -> cur_seq_no );
if ( pls -> cur_seq_no < pls -> start_seq_no + pls -> n_segments - 1 ) {
pls -> cur_seq_no += 1 ;
}
pls -> needed = 1 ;
}
/* Open the demuxer for each playlist */
for ( i = 0 ; i < c -> n_playlists ; i ++ ) {
struct playlist * pls = c -> playlists [ i ];
AVInputFormat * in_fmt = NULL ;
av_log ( NULL , AV_LOG_INFO , "LNTES----------->222222222i = 0; i < c->n_playlists; i++ \n " );
if ( pls -> ctx )
{
av_log ( NULL , AV_LOG_INFO , "LNTES----------->1111111111i = 0; i < c->n_playlists; i++ \n " );
continue ;
}
if ( ! ( pls -> ctx = avformat_alloc_context ())) {
ret = AVERROR ( ENOMEM );
goto fail ;
}
if ( pls -> n_segments == 0 )
continue ;
pls -> index = i ;
pls -> needed = 1 ;
pls -> parent = s ;
pls -> ctx -> interrupt_callback = s -> interrupt_callback ;
pls -> switch_variant_requested = - 1 ;
pls -> read_buffer = av_malloc ( INITIAL_BUFFER_SIZE );
if ( ! pls -> read_buffer ) {
ret = AVERROR ( ENOMEM );
avformat_free_context ( pls -> ctx );
pls -> ctx = NULL ;
goto fail ;
}
ffio_init_context ( & pls -> pb , pls -> read_buffer , INITIAL_BUFFER_SIZE , 0 , pls ,
read_data , NULL , NULL );
//pls->pb.seekable = 0;
av_log ( NULL , AV_LOG_INFO , "LNTES----------->1111111111pls->segments[0]->url:%s \n " , pls -> segments [ 0 ] -> url );
ret = av_probe_input_buffer ( & pls -> pb , & in_fmt , pls -> segments [ 0 ] -> url ,
NULL , 0 , 0 );
if ( ret < 0 ) {
/* Free the ctx - it isn't initialized properly at this point,
* so avformat_close_input shouldn't be called. If
* avformat_open_input fails below, it frees and zeros the
* context, so it doesn't need any special treatment like this. */
av_log ( NULL , AV_LOG_ERROR , "Error when loading first segment '%s', ret:%d \n " , pls -> segments [ 0 ] -> url , ret );
avformat_free_context ( pls -> ctx );
pls -> ctx = NULL ;
goto fail ;
}
pls -> ctx -> pb = & pls -> pb ;
pls -> ctx -> io_open = nested_io_open ;
pls -> ctx -> flags |= s -> flags & ~ AVFMT_FLAG_CUSTOM_IO ;
if (( ret = ff_copy_whiteblacklists ( pls -> ctx , s )) < 0 )
goto fail ;
AVDictionary * opts = NULL ;
av_dict_set_int ( & opts , "hls_live_mode" , ! pls -> finished , 0 );
av_dict_set_int ( & opts , "sidx_reference_count" , 0 , 0 );
ret = avformat_open_input ( & pls -> ctx , pls -> segments [ 0 ] -> url , in_fmt , & opts );
av_dict_free ( & opts );
if ( ret < 0 )
goto fail ;
if ( pls -> id3_deferred_extra && pls -> ctx -> nb_streams == 1 ) {
ff_id3v2_parse_apic ( pls -> ctx , & pls -> id3_deferred_extra );
avformat_queue_attached_pictures ( pls -> ctx );
ff_id3v2_parse_priv ( pls -> ctx , & pls -> id3_deferred_extra );
ff_id3v2_free_extra_meta ( & pls -> id3_deferred_extra );
pls -> id3_deferred_extra = NULL ;
}
if ( pls -> is_id3_timestamped == - 1 )
av_log ( NULL , AV_LOG_WARNING , "No expected HTTP requests have been made \n " );
/*
* For ID3 timestamped raw audio streams we need to detect the packet
* durations to calculate timestamps in fill_timing_for_id3_timestamped_stream(),
* but for other streams we can rely on our user calling avformat_find_stream_info()
* on us if they want to.
*/
if ( pls -> is_id3_timestamped ) {
ret = avformat_find_stream_info ( pls -> ctx , NULL );
if ( ret < 0 )
goto fail ;
}
pls -> has_noheader_flag = !! ( pls -> ctx -> ctx_flags & AVFMTCTX_NOHEADER );
/* Create new AVStreams for each stream in this playlist */
av_log ( NULL , AV_LOG_INFO , "-------->update_streams_from_subdemuxer \n " );
ret = update_streams_from_subdemuxer ( s , pls );
if ( ret < 0 )
goto fail ;
/*
* Copy any metadata from playlist to main streams, but do not set
* event flags.
*/
if ( pls -> n_main_streams )
av_dict_copy ( & pls -> main_streams [ 0 ] -> metadata , pls -> ctx -> metadata , 0 );
add_metadata_from_renditions ( s , pls , AVMEDIA_TYPE_AUDIO );
add_metadata_from_renditions ( s , pls , AVMEDIA_TYPE_VIDEO );
add_metadata_from_renditions ( s , pls , AVMEDIA_TYPE_SUBTITLE );
}
update_noheader_flag ( s );
return 0 ;
fail :
av_log ( NULL , AV_LOG_ERROR , "hls_switch_to_i_frame_variant err" );
c -> varrant_used = varrant_used_last ;
variant = c -> variants [ c -> varrant_used ];
for ( i = 0 ; i < c -> n_playlists_all ; i ++ )
{
c -> playlists [ i ] = NULL ;
}
c -> n_playlists = variant -> n_playlists ;
for ( i = 0 ; i < variant -> n_playlists ; i ++ )
{
c -> playlists [ i ] = variant -> playlists [ i ];
}
return ret ;
}
static int judge_scale ( int scale )
{
int ret = scale ;
int symbol = ( scale >= 0 ? 1 : - 1 );
scale *= symbol ;
if ( scale > I_FRAME_FF_FB_STEP_4X && scale < I_FRAME_FF_FB_STEP_8X ) {
ret = I_FRAME_FF_FB_STEP_4X * symbol ;
} else if ( scale > I_FRAME_FF_FB_STEP_8X && scale < I_FRAME_FF_FB_STEP_16X ) {
ret = I_FRAME_FF_FB_STEP_8X * symbol ;
} else if ( scale > I_FRAME_FF_FB_STEP_16X && scale < I_FRAME_FF_FB_STEP_32X ) {
ret = I_FRAME_FF_FB_STEP_16X * symbol ;
} else if ( scale > I_FRAME_FF_FB_STEP_32X && scale < I_FRAME_FF_FB_STEP_64X ) {
ret = I_FRAME_FF_FB_STEP_32X * symbol ;
} else if ( scale > I_FRAME_FF_FB_STEP_64X ) {
ret = I_FRAME_FF_FB_STEP_64X * symbol ;
}
return ret ;
}
static int hls_read_fast ( struct AVFormatContext * s , int scale )
{
int ret = - 1 ;
do {
if ( NULL == s ) {
ret = AVERROR_INVALIDDATA ;
break ;
}
HLSContext * c = s -> priv_data ;
av_log ( NULL , AV_LOG_INFO , "LNTEST------------>ENTER hls_read_fast, c->i_frame_mode:%d, scale:%d \n " , c -> i_frame_mode , scale );
if ( scale > I_FRAME_FF_FB_STEP_64X || scale < - I_FRAME_FF_FB_STEP_64X ) {
av_log ( NULL , AV_LOG_ERROR , "bad param, scale %d is invalid! \n " , scale );
ret = AVERROR_INVALIDDATA ;
break ;
}
scale = judge_scale ( scale );
if ( c -> i_frame_mode ) {
if ( scale == 0 ) {
ret = hls_switch_I_frame_back_to_normal_variant ( s , 0 );
if ( ret >= 0 ) {
c -> i_frame_mode = 0 ;
c -> i_frame_ff_fb_scale = 0 ;
}
av_log ( NULL , AV_LOG_INFO , "LNTEST------------>ENTER c->i_frame_mode && scale == 0 \n " );
} else {
c -> i_frame_ff_fb_scale = scale ;
av_log ( NULL , AV_LOG_INFO , "LNTEST------------>ENTER scale: %d \n " , scale );
ret = 0 ;
}
} else {
2026-08-25 10:02:36 +08:00
int best_index = find_best_i_frame_variant ( c , c -> playlists [ 0 ] -> url , ( const struct variant_t * const * ) c -> i_frame_variants , c -> n_i_frame_variants );
2026-08-13 11:28:40 +08:00
if ( best_index >= 0 ) {
ret = hls_switch_to_i_frame_variant ( s , best_index );
if ( ret >= 0 ) {
c -> i_frame_ff_fb_scale = scale ;
c -> i_frame_mode = 1 ;
}
break ;
} else {
for ( int i = 0 ; i <= c -> n_i_frame_variants - 1 ; i ++ )
{
av_log ( NULL , AV_LOG_INFO , "LNTEST-------->used is %d, change to %d \n " , c -> varrant_used , i );
ret = hls_switch_to_i_frame_variant ( s , i );
if ( ret >= 0 ) {
c -> i_frame_ff_fb_scale = scale ;
c -> i_frame_mode = 1 ;
}
break ;
}
}
}
} while ( 0 );
return ret ;
}
/*
* return: >=0 success; <0: failed;
*/
static int find_best_i_frame_variant ( const HLSContext * c , const char * playing_url , const struct variant_t ** variants , int n_variants ){
char * last_pos = NULL ;
unsigned int base_url_len = 0 ;
int ret = - 1 ;
do {
if ( n_variants <= 0
|| variants == NULL
|| playing_url == NULL ) {
break ;
}
if ( n_variants == 1 ) {
ret = 0 ;
break ;
}
last_pos = strrchr ( playing_url , '/' );
if ( NULL == last_pos ) {
av_log ( NULL , AV_LOG_ERROR , "can not find / in %s \n " , playing_url );
break ;
}
base_url_len = last_pos - playing_url ;
if ( base_url_len <= 0 ) {
av_log ( NULL , AV_LOG_ERROR , "base_url_len <= 0, playing_url is %s \n " , playing_url );
break ;
}
for ( int i = 0 ; i < n_variants ; i ++ ) {
struct variant_t * variant = variants [ i ];
struct playlist * pls = variant -> playlists [ 0 ];
char * url = pls -> url ;
if ( NULL == url )
{
av_log ( NULL , AV_LOG_ERROR , "the index of %d in i_frame_variants playlists[0]->url is NULL \n " );
continue ;
}
if ( strncmp ( url , playing_url , base_url_len ) == 0 )
{
ret = i ;
break ;
} else {
av_log ( NULL , AV_LOG_INFO , "playing_url:%s, url:%s \n " , playing_url , url );
}
}
} while ( 0 );
return ret ;
}
#endif
AVInputFormat ff_hls_demuxer = {
. name = "hls" ,
. long_name = NULL_IF_CONFIG_SMALL ( "Apple HTTP Live Streaming" ),
. priv_class = & hls_class ,
. priv_data_size = sizeof ( HLSContext ),
. flags = AVFMT_NOGENSEARCH | AVFMT_TS_DISCONT ,
. read_probe = hls_probe ,
. read_header = hls_read_header ,
. read_packet = hls_read_packet ,
. read_close = hls_close ,
. read_seek = hls_read_seek ,
};
AmlInputFormat aml_hls_demuxer = {
//.name = "hls,applehttp",
//.long_name = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming"),
//.priv_class = &hls_class,
//.priv_data_size = sizeof(HLSContext),
. flags = AVFMT_NOGENSEARCH ,
. read_probe = hls_probe ,
. read_header = hls_read_header ,
. read_packet = hls_read_packet ,
. read_close = hls_close ,
. read_seek = hls_read_seek ,
#ifdef HLS_I_FRAME_MODE
. read_fast = hls_read_fast ,
#else
. read_fast = NULL ,
#endif
};