448 lines
17 KiB
C
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
|