Files
2026-08-24 10:03:24 +08:00

448 lines
17 KiB
C

#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 <float.h>
//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