#ifndef FFHLS_H #define FFHLS_H #ifdef __cplusplus extern "C" { #endif //#include "libavformat/http.h" #include "libavutil/avstring.h" #include "libavutil/avassert.h" #include "libavutil/intreadwrite.h" #include "libavutil/mathematics.h" #include "libavutil/opt.h" #include "libavutil/dict.h" #include "libavutil/time.h" #include "libavutil/internal.h" #include "libavformat/avformat.h" #include "libavformat/internal.h" #include "libavformat/avio_internal.h" #include "libavformat/id3v2.h" #include "hls_bandwidth.h" #include "libavformat/aml/utils/ffmpegObMsgheader.h" #include //add for i frame #ifndef HLS_I_FRAME_MODE #define HLS_I_FRAME_MODE 1 #endif #define HLS_MAX_VIDEO_STREAMS 10 #define HLS_MAX_AUDIO_STREAMS 15 #define HLS_MAX_SUB_STREAMS 10 #define INITIAL_BUFFER_SIZE 32768 #define MAX_FIELD_LEN 64 #define MAX_CHARACTERISTICS_LEN 512 #define MPEG_TIME_BASE 90000 #define MPEG_TIME_BASE_Q \ (AVRational) { 1, MPEG_TIME_BASE } /* * An apple http stream consists of a playlist with media segment files, * played sequentially. There may be several playlists with the same * video content, in different bandwidth variants, that are played in * parallel (preferably only one bandwidth variant at a time). In this case, * the user supplied the url to a main playlist that only lists the variant * playlists. * * If the main playlist doesn't point at any variants, we still create * one anonymous toplevel variant for this, to maintain the structure. */ typedef void (*hls_notify_t)(void *handle, int msg, int64_t ext1, int64_t ext2, int64_t ex3); typedef void (*hls_notify_direct_t)(void *handle, int msg, int64_t ext1, int64_t ext2, int64_t ex3); typedef enum { HLS_MODE_NULL, HLS_MODE_VOD, HLS_MODE_LIVE, HLS_MODE_LIVE_SHIFT, }HLS_MODE; typedef enum { HLS_LIVE_refreshed, HLS_LIVE_norefresh_1s_1, HLS_LIVE_norefresh_1s_2, HLS_LIVE_norefresh_1s_3, HLS_LIVE_norefresh_2s, HLS_LIVE_norefresh_5s, HLS_LIVE_norefresh_10s, }HLS_LIVE_refresh_STATE; enum KeyType { KEY_NONE, KEY_AES_128, KEY_SAMPLE_AES, KEY_SAMPLE_AES_CTR, }; struct segment { int64_t duration; int64_t url_offset; int64_t size; char *url; char *key; enum KeyType key_type; uint8_t iv[16]; /* associated Media Initialization Section, treated as a segment */ struct segment *init_section; int64_t last_readtime; int64_t fetch_starttime; int64_t timestamp_us_in_url; int64_t timestamp_us_in_url_next; }; struct hls_key_info { char uri[MAX_URL_SIZE]; char method[11]; char keyid[35]; char iv[35]; }; struct rendition; enum PlaylistType { PLS_TYPE_UNSPECIFIED, PLS_TYPE_EVENT, PLS_TYPE_VOD }; #ifdef HLS_I_FRAME_MODE enum I_FRAME_FF_FB_STEP_E { I_FRAME_FF_FB_STEP_2X = 1, I_FRAME_FF_FB_STEP_4X = 2, I_FRAME_FF_FB_STEP_8X = 4, I_FRAME_FF_FB_STEP_16X = 8, I_FRAME_FF_FB_STEP_32X = 16, I_FRAME_FF_FB_STEP_64X = 32, }; struct i_frame_variant_info { char bandwidth[20]; /* variant group ids: */ char audio[MAX_FIELD_LEN]; char video[MAX_FIELD_LEN]; char subtitles[MAX_FIELD_LEN]; char uri[MAX_URL_SIZE]; char program_id[MAX_FIELD_LEN]; }; #endif enum InitSectionUpdateStatus { INIT_SEC_UPDATE_NONE, INIT_SEC_UPDATE_WAITING_REOPEN, INIT_SEC_UPDATE_REOPEN_DONE, }; /* * Each playlist has its own demuxer. If it currently is active, * it has an open AVIOContext too, and potentially an AVPacket * containing the next packet from this stream. */ struct playlist { char url[MAX_URL_SIZE]; AVIOContext pb; uint8_t *read_buffer; AVIOContext *input; int input_read_done; AVIOContext *input_next; int input_next_requested; AVFormatContext *parent; int index; AVFormatContext *ctx; AVPacket pkt; int has_noheader_flag; /* main demuxer streams associated with this playlist * indexed by the subdemuxer stream indexes */ AVStream **main_streams; int n_main_streams; int finished; enum PlaylistType type; int64_t target_duration; int start_seq_no; int n_segments; struct segment **segments; int needed; int cur_seq_no; int64_t cur_seg_offset; int64_t last_load_time; /* Currently active Media Initialization Section */ struct segment *cur_init_section; uint8_t *init_sec_buf; unsigned int init_sec_buf_size; unsigned int init_sec_data_len; unsigned int init_sec_buf_read_offset; enum InitSectionUpdateStatus init_section_update_status; int init_section_update_notify; int64_t first_packet_pts; // save pts of first packet in segment int use_segment_timestamp; int use_segment_timestamp_increment_step; int parse_preloaded_segment_id3_requested;/* The usage of variable requires enabling the feature of preloading the first segment(read_packet_readed).*/ int each_segment_read_done; int reopen_playlist_requested; int reopen_playlist_enable; int switch_variant_requested; char key_url[MAX_URL_SIZE]; uint8_t key[16]; /* ID3 timestamp handling (elementary audio streams have ID3 timestamps * (and possibly other ID3 tags) in the beginning of each segment) */ int is_id3_timestamped; /* -1: not yet known */ int64_t id3_mpegts_timestamp; /* in mpegts tb */ int64_t id3_offset; /* in stream original tb */ uint8_t *id3_buf; /* temp buffer for id3 parsing */ unsigned int id3_buf_size; AVDictionary *id3_initial; /* data from first id3 tag */ int id3_found; /* ID3 tag found at some point */ int id3_changed; /* ID3 tag data has changed at some point */ ID3v2ExtraMeta *id3_deferred_extra; /* stored here until subdemuxer is opened */ int64_t seek_timestamp; int seek_flags; int seek_stream_index; /* into subdemuxer stream array */ /* Renditions associated with this playlist, if any. * Alternative rendition playlists have a single rendition associated * with them, and variant main Media Playlists may have * multiple (playlist-less) renditions associated with them. */ int n_renditions; struct rendition **renditions; /* Media Initialization Sections (EXT-X-MAP) associated with this * playlist, if any. */ int n_init_sections; struct segment **init_sections; //open in hls thread //set playlist_pb value when all m3u8 had read in hls thread //parse playlist in read_data when need open new seg and the value is not NULL in live mode AVIOContext *playlist_pb; char *redirect_url; char *timeshif_url; int64_t fetch_start; int64_t lastread_time; int64_t read_retrytime; int64_t open_retrytime; }; /* * Renditions are e.g. alternative subtitle or audio streams. * The rendition may either be an external playlist or it may be * contained in the main Media Playlist of the variant (in which case * playlist is NULL). */ struct rendition { enum AVMediaType type; struct playlist *playlist; char group_id[MAX_FIELD_LEN]; char language[MAX_FIELD_LEN]; char name[MAX_FIELD_LEN]; int disposition; }; struct variant_t { int bandwidth; /* every variant contains at least the main Media Playlist in index 0 */ int n_playlists; struct playlist **playlists; char audio_group[MAX_FIELD_LEN]; char video_group[MAX_FIELD_LEN]; char subtitles_group[MAX_FIELD_LEN]; char codecs[MAX_FIELD_LEN]; char resolution[MAX_FIELD_LEN]; int containVideo; }; typedef struct HLSContext { AVClass *clazz; AVFormatContext *ctx; int n_variants; struct variant_t **variants; int n_playlists; struct playlist **playlists; int n_playlists_all; struct playlist **playlists_all; int n_renditions; struct rendition **renditions; int cur_seq_no; int live_start_index; int first_packet; int64_t first_timestamp; int64_t cur_timestamp; int64_t shift_timestamp; AVIOInterruptCB *interrupt_callback; char *referer; ///< holds HTTP referer set as an AVOption to the HTTP protocol context char *user_agent; ///< holds HTTP user agent set as an AVOption to the HTTP protocol context char *cookies; ///< holds HTTP cookie values set in either the initial response or as an AVOption to the HTTP protocol context char *headers; ///< holds HTTP headers set as an AVOption to the HTTP protocol context char *http_proxy; ///< holds the address of the HTTP proxy server AVDictionary *avio_opts; int strict_std_compliance; char *allowed_extensions; int max_reload; int http_persistent; int http_multiple; AVIOContext *playlist_pb; //hlsphlsParser handle void *session; //for index change when switch BW int index_defaule_max; int index_defaule_audio; int index_defaule_sub; int index_defaule_video; //for http and tcp int seekable; int http_reconnect; int http_reconnect_at_eof; int http_reconnect_streamed; int http_reconnect_delay_max; int http_always_reconnect; int http_thread_enable; int hls_switch_pmt; int hls_thread_enable; int http_thread_bufsize; int tcp_rw_timeout; int tcp_connect_timeout; int addrinfo_one_by_one; int dns_seq_mode; int addrinfo_timeout; int dns_cache_timeout; //band width HLS_BandWidth* hlsBandWidth; int varrant_used; int quality; //0: high; 1: low; 2: Auto high begin; 3: Auto low begin; int bw_limit; int bw_adjust_target; int bw_adjust_limit; //i frame mode #ifdef HLS_I_FRAME_MODE int i_frame_mode;//0: i_frame_mode closed 1: i_frame_mode_started int n_i_frame_variants; struct variant_t **i_frame_variants; int i_frame_ff_fb_scale; #endif int vod_fastseek; int print_m3u8; HLS_MODE hlsLiveMode; void * AMLSleep; int64_t expire_time; int enableRawData; hls_notify_t hls_notify; hls_notify_t hls_notify_direct; int64_t observer; int32_t retry_segment_cnt; int net_down_wait_parse_playlist; int customer_hls_mode;//get from url,0:null 1:live mode 2:timeshift mode 3:vod mode enum KeyType ctx_key_type; int update_pssh; int lowest_valid_variant; void* parserHandle; void (*m3u8UpdateFunc)(void*, int); int read_packet_readed; int read_data_error;//record error value of read_data int hls_optimize_segment_timestamp_enable;//ometimes the original segment PTS is abnormal. The optimize PTS switch is related to timestamp_us_in_url_next and timestamp_us_in_url. } HLSContext; #define OFFSET(x) offsetof(HLSContext, x) #define FLAGS AV_OPT_FLAG_DECODING_PARAM #define D AV_OPT_FLAG_DECODING_PARAM #define E AV_OPT_FLAG_ENCODING_PARAM static const AVOption hls_options[] = { {"live_start_index", "segment index to start live streams at (negative values are from the end)", OFFSET(live_start_index), AV_OPT_TYPE_INT, {.i64 = 0}, INT_MIN, INT_MAX, FLAGS}, {"allowed_extensions", "List of file extensions that hls is allowed to access", OFFSET(allowed_extensions), AV_OPT_TYPE_STRING, {.str = "3gp,aac,avi,flac,mkv,m3u8,m4a,m4s,m4v,mpg,mov,mp2,mp3,mp4,mpeg,mpegts,ogg,ogv,oga,ts,vob,wav"}, INT_MIN, INT_MAX, FLAGS}, {"max_reload", "Maximum number of times a insufficient list is attempted to be reloaded", OFFSET(max_reload), AV_OPT_TYPE_INT, {.i64 = 1000}, 0, INT_MAX, FLAGS}, {"http_persistent", "Use persistent HTTP connections", OFFSET(http_persistent), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, FLAGS}, {"http_multiple", "Use multiple HTTP connections for fetching segments", OFFSET(http_multiple), AV_OPT_TYPE_BOOL, {.i64 = 0}, -1, 1, FLAGS}, {"seekable", "control seekability of connection", OFFSET(seekable), AV_OPT_TYPE_BOOL, { .i64 = -1 }, -1, 1, D }, {"http_reconnect", "auto reconnect after disconnect before EOF", OFFSET(http_reconnect), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, D }, {"http_reconnect_at_eof", "auto reconnect at EOF", OFFSET(http_reconnect_at_eof), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D }, {"http_reconnect_streamed", "auto reconnect streamed / non seekable streams", OFFSET(http_reconnect_streamed), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, D }, {"http_reconnect_delay_max", "max reconnect delay in seconds after which to give up", OFFSET(http_reconnect_delay_max), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, UINT_MAX/1000/1000, D }, {"addrinfo_one_by_one", "parse addrinfo one by one in getaddrinfo()", OFFSET(addrinfo_one_by_one), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, .flags = D|E }, {"tcp_rw_timeout", "set timeout (in microseconds) of socket I/O operations", OFFSET(tcp_rw_timeout), AV_OPT_TYPE_INT, { .i64 = 5000000 }, -1, INT_MAX, .flags = D|E }, {"tcp_connect_timeout", "set connect timeout (in microseconds) of socket", OFFSET(tcp_connect_timeout), AV_OPT_TYPE_INT, { .i64 = 1000 }, -1, INT_MAX, .flags = D|E }, {"dns_seq_mode", "the seq to get IP from DNS list", OFFSET(dns_seq_mode), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, .flags = D|E }, {"addrinfo_timeout", "set timeout (in microseconds) for getaddrinfo()", OFFSET(addrinfo_timeout), AV_OPT_TYPE_INT, { .i64 = 5000000 }, -1, INT_MAX, .flags = D|E }, {"dns_cache_timeout", "dns cache TTL (in second)", OFFSET(dns_cache_timeout), AV_OPT_TYPE_INT, { .i64 = 50000 }, -1, INT_MAX, .flags = D|E }, {"http_thread_enable", "enable thead for protocol", OFFSET(http_thread_enable), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, .flags = D|E }, {"http_thread_bufsize", "MAX buf size for avio buf", OFFSET(http_thread_bufsize), AV_OPT_TYPE_INT, { .i64 = 1048576 }, 0, INT_MAX, .flags = D|E }, {"hls_thread_enable", "enable thead for hls", OFFSET(hls_thread_enable), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, INT_MAX, .flags = D|E }, {"hls_quality", "default quality", OFFSET(quality), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, .flags = D|E }, {"hls_bw_limit", "bandwidth limit to switch lowest", OFFSET(bw_limit), AV_OPT_TYPE_INT, { .i64 = 3 }, 2, INT_MAX, .flags = D|E }, {"hls_bw_adjust_target", "bandwidth adjust target dur", OFFSET(bw_adjust_target), AV_OPT_TYPE_INT, { .i64 = 1 }, 1, INT_MAX, .flags = D|E }, {"hls_bw_adjust_limit", "bandwidth adjust limit dur", OFFSET(bw_adjust_limit), AV_OPT_TYPE_INT, { .i64 = 2 }, 1, INT_MAX, .flags = D|E }, {"hls_vod_fastseek", "VOD seek, read from first pkt", OFFSET(vod_fastseek), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 1, .flags = D|E }, {"hls_print_m3u8", "bandwidth adjust limit dur", OFFSET(print_m3u8), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, .flags = D|E }, {"hls_retry_segment_cnt", "set reopen segment cnt", OFFSET(retry_segment_cnt), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, .flags = D|E }, {"headers", "set custom HTTP headers, can override built in default headers", OFFSET(headers), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, D | E }, {"hls_switch_pmt", "support pmt change or not", OFFSET(hls_switch_pmt), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, .flags = D|E }, {"update_pssh", "update_pssh", OFFSET(update_pssh), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 2, D }, {"observer", "pre set observer, so can use it directly when read_header", OFFSET(observer), AV_OPT_TYPE_INT64, { .i64 = 0 }, DBL_MIN, DBL_MAX, D }, // todo, fix me, need consider 64 bit system {NULL}}; static const AVClass hls_class = { .class_name = "hls,applehttp", .item_name = av_default_item_name, .option = hls_options, .version = LIBAVUTIL_VERSION_INT, }; int64_t time_to_shift(HLSContext *c, int varrant_used); int hls_read_shift(AVFormatContext *s, char *shift_url, int varrant_used); 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); int make_timeshift_url(char *url, char *shift_url, int64_t us, int varrant_used); 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 parse_playlist_start_seq(AVIOContext *in); void free_avio_list(AVFormatContext *s, AVIOContext ***avio, int *n_avio, int need_close); int hls_switch_variant(AVFormatContext *s, int varrant_used); #ifdef HLS_I_FRAME_MODE int hls_switch_I_frame_back_to_normal_variant(AVFormatContext *s, int varrant_used); int hls_switch_to_i_frame_variant(AVFormatContext *s, int varrant_used); static int reset_playlist(HLSContext *c, struct playlist *pls); static struct variant_t *new_i_frame_variant(HLSContext *c, struct i_frame_variant_info *info, const char *base); static void handle_i_frame_variant_args(struct i_frame_variant_info *info, const char *key, int key_len, char **dest, int *dest_len); static int hls_read_fast(struct AVFormatContext *s, int scale); static int judge_scale(int scale); static void judge_next_segment(struct playlist *pls); static int frames_in_ff_fb(int scale); static int i_frame_find_drop_interval(struct playlist *pls, int scale, int I_total_number); static int find_best_i_frame_variant(const HLSContext *c, const char* playing_url, const struct variant_t ** variants, int n_variants); #endif #ifdef __cplusplus } #endif #endif // FFHLS_H