|
|
|
@ -30,6 +30,35 @@ |
|
|
|
static struct timerthread media_player_thread; |
|
|
|
static __thread MYSQL *mysql_conn; |
|
|
|
|
|
|
|
|
|
|
|
struct media_player_cache_index { |
|
|
|
struct media_player_content_index index; |
|
|
|
struct rtp_payload_type dst_pt; |
|
|
|
}; |
|
|
|
struct media_player_cache_entry { |
|
|
|
bool finished; |
|
|
|
// "unfinished" elements, only used while decoding is active: |
|
|
|
mutex_t lock; |
|
|
|
cond_t cond; // to wait for more data to be decoded |
|
|
|
|
|
|
|
GPtrArray *packets; // read-only except for decoder thread, which uses finished flags and locks |
|
|
|
|
|
|
|
struct codec_scheduler csch; |
|
|
|
struct media_player_coder coder; // de/encoder data |
|
|
|
|
|
|
|
char *info_str; // for logging |
|
|
|
}; |
|
|
|
struct media_player_cache_packet { |
|
|
|
char *buf; |
|
|
|
str s; |
|
|
|
long long pts; |
|
|
|
long long duration; |
|
|
|
long long duration_ts; |
|
|
|
}; |
|
|
|
|
|
|
|
static mutex_t media_player_cache_lock; |
|
|
|
static GHashTable *media_player_cache; // keys and values only ever freed at shutdown |
|
|
|
|
|
|
|
static void media_player_read_packet(struct media_player *mp); |
|
|
|
#endif |
|
|
|
|
|
|
|
@ -85,6 +114,13 @@ static void media_player_shutdown(struct media_player *mp) { |
|
|
|
|
|
|
|
mp->media = NULL; |
|
|
|
media_player_coder_shutdown(&mp->coder); |
|
|
|
|
|
|
|
mp->cache_index.type = MP_OTHER; |
|
|
|
if (mp->cache_index.file.s) |
|
|
|
g_free(mp->cache_index.file.s); |
|
|
|
mp->cache_index.file = STR_NULL; |
|
|
|
mp->cache_entry = NULL; |
|
|
|
mp->cache_read_idx = 0; |
|
|
|
} |
|
|
|
#endif |
|
|
|
|
|
|
|
@ -103,13 +139,13 @@ long long media_player_stop(struct media_player *mp) { |
|
|
|
static void __media_player_free(void *p) { |
|
|
|
struct media_player *mp = p; |
|
|
|
|
|
|
|
//ilog(LOG_DEBUG, "freeing media_player"); |
|
|
|
|
|
|
|
media_player_shutdown(mp); |
|
|
|
ssrc_ctx_put(&mp->ssrc_out); |
|
|
|
mutex_destroy(&mp->lock); |
|
|
|
obj_put(mp->call); |
|
|
|
av_packet_free(&mp->coder.pkt); |
|
|
|
if (mp->cache_index.file.s) |
|
|
|
g_free(mp->cache_index.file.s); |
|
|
|
} |
|
|
|
#endif |
|
|
|
|
|
|
|
@ -134,6 +170,7 @@ struct media_player *media_player_new(struct call_monologue *ml) { |
|
|
|
mp->call = obj_get(ml->call); |
|
|
|
mp->ml = ml; |
|
|
|
mp->seq = ssl_random(); |
|
|
|
mp->buffer_ts = ssl_random(); |
|
|
|
mp->ssrc_out = ssrc_ctx; |
|
|
|
|
|
|
|
mp->coder.pkt = av_packet_alloc(); |
|
|
|
@ -149,9 +186,6 @@ struct media_player *media_player_new(struct call_monologue *ml) { |
|
|
|
|
|
|
|
static void __send_timer_free(void *p) { |
|
|
|
struct send_timer *st = p; |
|
|
|
|
|
|
|
//ilog(LOG_DEBUG, "freeing send_timer"); |
|
|
|
|
|
|
|
obj_put(st->call); |
|
|
|
} |
|
|
|
|
|
|
|
@ -165,8 +199,6 @@ static void __send_timer_send_later(struct timerthread_queue *ttq, void *p) { |
|
|
|
|
|
|
|
// call->master_lock held in W |
|
|
|
struct send_timer *send_timer_new(struct packet_stream *ps) { |
|
|
|
//ilog(LOG_DEBUG, "creating send_timer"); |
|
|
|
|
|
|
|
struct send_timer *st = timerthread_queue_new("send_timer", sizeof(*st), |
|
|
|
&send_timer_thread, |
|
|
|
__send_timer_send_now, |
|
|
|
@ -321,6 +353,296 @@ static void media_player_coder_add_packet(struct media_player_coder *c, |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void media_player_read_decoded_packet(struct media_player *mp) { |
|
|
|
struct media_player_cache_entry *entry = mp->cache_entry; |
|
|
|
|
|
|
|
unsigned int read_idx = mp->cache_read_idx; |
|
|
|
ilog(LOG_DEBUG, "Buffered media player reading packet #%u", read_idx); |
|
|
|
|
|
|
|
retry:; |
|
|
|
bool finished = entry->finished; // hold lock or not |
|
|
|
|
|
|
|
if (!finished) { |
|
|
|
// slow track with locking |
|
|
|
mutex_lock(&entry->lock); |
|
|
|
// confirm that we are indeed not finished |
|
|
|
if (entry->finished) { |
|
|
|
// preempted, good to go |
|
|
|
mutex_unlock(&entry->lock); |
|
|
|
finished = true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (read_idx >= entry->packets->len) { |
|
|
|
if (!finished) { |
|
|
|
// wait for more |
|
|
|
cond_wait(&entry->cond, &entry->lock); |
|
|
|
mutex_unlock(&entry->lock); |
|
|
|
goto retry; |
|
|
|
} |
|
|
|
|
|
|
|
// EOF |
|
|
|
|
|
|
|
if (mp->repeat <= 1) { |
|
|
|
ilog(LOG_DEBUG, "EOF reading from media buffer (%s), stopping playback", |
|
|
|
entry->info_str); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
ilog(LOG_DEBUG, "EOF reading from media buffer (%s) but will repeat %li time", |
|
|
|
entry->info_str, mp->repeat); |
|
|
|
mp->repeat--; |
|
|
|
read_idx = mp->cache_read_idx = 0; |
|
|
|
goto retry; |
|
|
|
} |
|
|
|
|
|
|
|
// got a packet |
|
|
|
struct media_player_cache_packet *pkt = entry->packets->pdata[read_idx]; |
|
|
|
long long us_dur = pkt->duration; |
|
|
|
|
|
|
|
mp->cache_read_idx++; |
|
|
|
|
|
|
|
if (!finished) |
|
|
|
mutex_unlock(&entry->lock); |
|
|
|
|
|
|
|
// make a copy to send out |
|
|
|
size_t len = pkt->s.len + sizeof(struct rtp_header) + RTP_BUFFER_TAIL_ROOM; |
|
|
|
char *buf = g_malloc(len); |
|
|
|
memcpy(buf, pkt->buf, len); |
|
|
|
|
|
|
|
struct media_packet packet = { |
|
|
|
.tv = rtpe_now, |
|
|
|
.call = mp->call, |
|
|
|
.media = mp->media, |
|
|
|
.media_out = mp->media, |
|
|
|
.rtp = (void *) buf, |
|
|
|
.ssrc_out = mp->ssrc_out, |
|
|
|
}; |
|
|
|
|
|
|
|
mp->last_frame_ts = pkt->pts; |
|
|
|
|
|
|
|
codec_output_rtp(&packet, &entry->csch, mp->coder.handler, buf, pkt->s.len, mp->buffer_ts, |
|
|
|
read_idx == 0, mp->seq++, 0, -1, 0); |
|
|
|
|
|
|
|
mp->buffer_ts += pkt->duration_ts; |
|
|
|
mp->sync_ts_tv = rtpe_now; |
|
|
|
|
|
|
|
media_packet_encrypt(mp->crypt_handler->out->rtp_crypt, mp->sink, &packet); |
|
|
|
|
|
|
|
mutex_lock(&mp->sink->out_lock); |
|
|
|
if (media_socket_dequeue(&packet, mp->sink)) |
|
|
|
ilog(LOG_ERR, "Error sending playback media to RTP sink"); |
|
|
|
mutex_unlock(&mp->sink->out_lock); |
|
|
|
|
|
|
|
// schedule our next run |
|
|
|
timeval_add_usec(&mp->next_run, us_dur); |
|
|
|
timerthread_obj_schedule_abs(&mp->tt_obj, &mp->next_run); |
|
|
|
} |
|
|
|
|
|
|
|
static void media_player_cached_reader_start(struct media_player *mp, const struct rtp_payload_type *dst_pt, |
|
|
|
long long repeat) |
|
|
|
{ |
|
|
|
struct media_player_cache_entry *entry = mp->cache_entry; |
|
|
|
|
|
|
|
// create dummy codec handler and start timer |
|
|
|
|
|
|
|
mp->coder.handler = codec_handler_make_dummy(&entry->coder.handler->dest_pt, mp->media); |
|
|
|
|
|
|
|
mp->run_func = media_player_read_decoded_packet; |
|
|
|
mp->next_run = rtpe_now; |
|
|
|
mp->coder.duration = entry->coder.duration; |
|
|
|
|
|
|
|
// if we played anything before, scale our sync TS according to the time |
|
|
|
// that has passed |
|
|
|
if (mp->sync_ts_tv.tv_sec) { |
|
|
|
long long ts_diff_us = timeval_diff(&rtpe_now, &mp->sync_ts_tv); |
|
|
|
mp->buffer_ts += fraction_divl(ts_diff_us * dst_pt->clock_rate / 1000000, &dst_pt->codec_def->default_clockrate_fact); |
|
|
|
} |
|
|
|
|
|
|
|
mp->sync_ts_tv = rtpe_now; |
|
|
|
mp->repeat = repeat; |
|
|
|
|
|
|
|
media_player_read_decoded_packet(mp); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void cache_packet_free(void *ptr) { |
|
|
|
struct media_player_cache_packet *p = ptr; |
|
|
|
g_free(p->buf); |
|
|
|
g_slice_free1(sizeof(*p), p); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// returns: true = entry exists, decoding handled separately, use entry for playback |
|
|
|
// false = no entry exists, OR entry is a new one, proceed to open decoder, then call _play_start |
|
|
|
static bool media_player_cache_get_entry(struct media_player *mp, |
|
|
|
const struct rtp_payload_type *dst_pt, long long repeat) |
|
|
|
{ |
|
|
|
if (!rtpe_config.player_cache) |
|
|
|
return false; |
|
|
|
if (mp->cache_index.type <= 0) |
|
|
|
return false; |
|
|
|
|
|
|
|
struct media_player_cache_index lookup; |
|
|
|
lookup.index = mp->cache_index; |
|
|
|
lookup.dst_pt = *dst_pt; |
|
|
|
|
|
|
|
mutex_lock(&media_player_cache_lock); |
|
|
|
struct media_player_cache_entry *entry = mp->cache_entry |
|
|
|
= g_hash_table_lookup(media_player_cache, &lookup); |
|
|
|
|
|
|
|
bool ret = true; // entry exists, use cached data |
|
|
|
if (entry) { |
|
|
|
media_player_cached_reader_start(mp, dst_pt, repeat); |
|
|
|
goto out; |
|
|
|
} |
|
|
|
|
|
|
|
ret = false; // new entry, open decoder, then call media_player_play_start |
|
|
|
|
|
|
|
// initialise object |
|
|
|
|
|
|
|
struct media_player_cache_index *ins_key = g_slice_alloc(sizeof(*ins_key)); |
|
|
|
*ins_key = lookup; |
|
|
|
str_init_dup_str(&ins_key->index.file, &lookup.index.file); |
|
|
|
codec_init_payload_type(&ins_key->dst_pt, MT_UNKNOWN); // duplicate contents |
|
|
|
entry = mp->cache_entry = g_slice_alloc0(sizeof(*entry)); |
|
|
|
mutex_init(&entry->lock); |
|
|
|
cond_init(&entry->cond); |
|
|
|
entry->packets = g_ptr_array_new_full(64, cache_packet_free); |
|
|
|
|
|
|
|
switch (lookup.index.type) { |
|
|
|
case MP_DB: |
|
|
|
entry->info_str = g_strdup_printf("DB media file #%llu", lookup.index.db_id); |
|
|
|
break; |
|
|
|
case MP_FILE: |
|
|
|
entry->info_str = g_strdup_printf("media file '" STR_FORMAT "'", |
|
|
|
STR_FMT(&lookup.index.file)); |
|
|
|
break; |
|
|
|
case MP_BLOB: |
|
|
|
entry->info_str = g_strdup_printf("binary media blob"); |
|
|
|
break; |
|
|
|
default:; |
|
|
|
} |
|
|
|
|
|
|
|
g_hash_table_insert(media_player_cache, ins_key, entry); |
|
|
|
|
|
|
|
out: |
|
|
|
mutex_unlock(&media_player_cache_lock); |
|
|
|
|
|
|
|
return ret; |
|
|
|
} |
|
|
|
|
|
|
|
static void media_player_cache_packet(struct media_player_cache_entry *entry, char *buf, size_t len, |
|
|
|
long long us_dur, unsigned long long pts) |
|
|
|
{ |
|
|
|
// synthesise fake RTP header and media_packet context |
|
|
|
|
|
|
|
struct rtp_header rtp = { |
|
|
|
.timestamp = pts, // taken verbatim by handler_func_playback w/o byte swap |
|
|
|
}; |
|
|
|
struct media_packet packet = { |
|
|
|
.rtp = &rtp, |
|
|
|
.cache_entry = entry, |
|
|
|
}; |
|
|
|
str_init_len(&packet.raw, buf, len); |
|
|
|
packet.payload = packet.raw; |
|
|
|
|
|
|
|
entry->coder.handler->handler_func(entry->coder.handler, &packet); |
|
|
|
} |
|
|
|
|
|
|
|
static void media_player_cache_entry_decoder_thread(void *p) { |
|
|
|
struct media_player_cache_entry *entry = p; |
|
|
|
|
|
|
|
ilog(LOG_DEBUG, "Launching media decoder thread for %s", entry->info_str); |
|
|
|
|
|
|
|
while (true) { |
|
|
|
// let us be cancelled |
|
|
|
thread_cancel_enable(); |
|
|
|
pthread_testcancel(); |
|
|
|
thread_cancel_disable(); |
|
|
|
|
|
|
|
int ret = av_read_frame(entry->coder.fmtctx, entry->coder.pkt); |
|
|
|
if (ret < 0) { |
|
|
|
if (ret != AVERROR_EOF) |
|
|
|
ilog(LOG_ERR, "Error while reading from media stream"); |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
media_player_coder_add_packet(&entry->coder, (void *) media_player_cache_packet, entry); |
|
|
|
|
|
|
|
av_packet_unref(entry->coder.pkt); |
|
|
|
} |
|
|
|
|
|
|
|
mutex_lock(&entry->lock); |
|
|
|
entry->finished = true; |
|
|
|
cond_broadcast(&entry->cond); |
|
|
|
mutex_unlock(&entry->lock); |
|
|
|
|
|
|
|
ilog(LOG_DEBUG, "Decoder thread for %s finished", entry->info_str); |
|
|
|
} |
|
|
|
|
|
|
|
static void packet_encoded_cache(encoder_t *enc, struct codec_ssrc_handler *ch, struct media_packet *mp, |
|
|
|
str *s, char *buf, unsigned int pkt_len) |
|
|
|
{ |
|
|
|
struct media_player_cache_entry *entry = mp->cache_entry; |
|
|
|
|
|
|
|
struct media_player_cache_packet *ep = g_slice_alloc0(sizeof(*ep)); |
|
|
|
|
|
|
|
*ep = (__typeof__(*ep)) { |
|
|
|
.buf = buf, |
|
|
|
.s = *s, |
|
|
|
.pts = enc->avpkt->pts, |
|
|
|
.duration_ts = enc->avpkt->duration, |
|
|
|
.duration = (long long) enc->avpkt->duration * 1000000LL |
|
|
|
/ entry->coder.handler->dest_pt.clock_rate, |
|
|
|
}; |
|
|
|
|
|
|
|
mutex_lock(&entry->lock); |
|
|
|
g_ptr_array_add(entry->packets, ep); |
|
|
|
|
|
|
|
cond_broadcast(&entry->cond); |
|
|
|
mutex_unlock(&entry->lock); |
|
|
|
} |
|
|
|
|
|
|
|
static int media_player_packet_cache(encoder_t *enc, void *u1, void *u2) { |
|
|
|
struct codec_ssrc_handler *ch = u1; |
|
|
|
struct media_packet *mp = u2; |
|
|
|
|
|
|
|
packet_encoded_packetize(enc, ch, mp, packet_encoded_cache); |
|
|
|
|
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// called from media_player_play_start, which is called after media_player_cache_get_entry returned true. |
|
|
|
// this can mean that either we don't have a cache entry and should continue normally, or if we |
|
|
|
// do have a cache entry, initialise it, set up the thread, take over decoding, and then proceed as a |
|
|
|
// media player consuming the data from the decoder thread. |
|
|
|
// returns: false = continue normally decode in-thread, true = take data from other thread |
|
|
|
static bool media_player_cache_entry_init(struct media_player *mp, const struct rtp_payload_type *dst_pt, |
|
|
|
long long repeat) |
|
|
|
{ |
|
|
|
struct media_player_cache_entry *entry = mp->cache_entry; |
|
|
|
if (!entry) |
|
|
|
return false; |
|
|
|
|
|
|
|
// steal coder data |
|
|
|
entry->coder = mp->coder; |
|
|
|
ZERO(mp->coder); |
|
|
|
mp->coder.duration = entry->coder.duration; // retain this for reporting |
|
|
|
|
|
|
|
entry->coder.handler->packet_encoded = media_player_packet_cache; |
|
|
|
|
|
|
|
// use low priority (10 nice) |
|
|
|
thread_create_detach_prio(media_player_cache_entry_decoder_thread, entry, NULL, 10, "mp decoder"); |
|
|
|
|
|
|
|
media_player_cached_reader_start(mp, dst_pt, repeat); |
|
|
|
|
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// find suitable output payload type |
|
|
|
static struct rtp_payload_type *media_player_get_dst_pt(struct media_player *mp) { |
|
|
|
struct rtp_payload_type *dst_pt = NULL; |
|
|
|
@ -541,6 +863,9 @@ static void media_player_play_start(struct media_player *mp, const struct rtp_pa |
|
|
|
if (__ensure_codec_handler(mp, dst_pt)) |
|
|
|
return; |
|
|
|
|
|
|
|
if (media_player_cache_entry_init(mp, dst_pt, repeat)) |
|
|
|
return; |
|
|
|
|
|
|
|
mp->next_run = rtpe_now; |
|
|
|
// give ourselves a bit of a head start with decoding |
|
|
|
timeval_add_usec(&mp->next_run, -50000); |
|
|
|
@ -563,6 +888,12 @@ int media_player_play_file(struct media_player *mp, const str *file, long long r |
|
|
|
if (!dst_pt) |
|
|
|
return -1; |
|
|
|
|
|
|
|
mp->cache_index.type = MP_FILE; |
|
|
|
str_init_dup_str(&mp->cache_index.file, file); |
|
|
|
|
|
|
|
if (media_player_cache_get_entry(mp, dst_pt, repeat)) |
|
|
|
return 0; |
|
|
|
|
|
|
|
char file_s[PATH_MAX]; |
|
|
|
snprintf(file_s, sizeof(file_s), STR_FORMAT, STR_FMT(file)); |
|
|
|
|
|
|
|
@ -574,7 +905,6 @@ int media_player_play_file(struct media_player *mp, const str *file, long long r |
|
|
|
|
|
|
|
media_player_play_start(mp, dst_pt, repeat, start_pos); |
|
|
|
|
|
|
|
|
|
|
|
return 0; |
|
|
|
#else |
|
|
|
return -1; |
|
|
|
@ -625,13 +955,14 @@ static int64_t __mp_avio_seek(void *opaque, int64_t offset, int whence) { |
|
|
|
return __mp_avio_seek_set(c, ((int64_t) c->blob->len) + offset); |
|
|
|
return AVERROR(EINVAL); |
|
|
|
} |
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// call->master_lock held in W |
|
|
|
int media_player_play_blob(struct media_player *mp, const str *blob, long long repeat, long long start_pos) { |
|
|
|
#ifdef WITH_TRANSCODING |
|
|
|
static int media_player_play_blob_id(struct media_player *mp, const str *blob, long long repeat, |
|
|
|
long long start_pos, long long db_id) |
|
|
|
{ |
|
|
|
const char *err; |
|
|
|
int av_ret = 0; |
|
|
|
|
|
|
|
@ -639,6 +970,21 @@ int media_player_play_blob(struct media_player *mp, const str *blob, long long r |
|
|
|
if (!dst_pt) |
|
|
|
return -1; |
|
|
|
|
|
|
|
if (db_id >= 0) { |
|
|
|
mp->cache_index.type = MP_DB; |
|
|
|
mp->cache_index.db_id = db_id; |
|
|
|
|
|
|
|
if (media_player_cache_get_entry(mp, dst_pt, repeat)) |
|
|
|
return 0; |
|
|
|
} |
|
|
|
else { |
|
|
|
mp->cache_index.type = MP_BLOB; |
|
|
|
str_init_dup_str(&mp->cache_index.file, blob); |
|
|
|
|
|
|
|
if (media_player_cache_get_entry(mp, dst_pt, repeat)) |
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
mp->coder.blob = str_dup(blob); |
|
|
|
err = "out of memory"; |
|
|
|
if (!mp->coder.blob) |
|
|
|
@ -677,8 +1023,18 @@ err: |
|
|
|
ilog(LOG_ERR, "Failed to start media playback from memory: %s", err); |
|
|
|
if (av_ret) |
|
|
|
ilog(LOG_ERR, "Error returned from libav: %s", av_error(av_ret)); |
|
|
|
return -1; |
|
|
|
} |
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
// call->master_lock held in W |
|
|
|
int media_player_play_blob(struct media_player *mp, const str *blob, long long repeat, long long start_pos) { |
|
|
|
#ifdef WITH_TRANSCODING |
|
|
|
return media_player_play_blob_id(mp, blob, repeat, start_pos, -1); |
|
|
|
#else |
|
|
|
return -1; |
|
|
|
#endif |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -753,7 +1109,7 @@ success:; |
|
|
|
|
|
|
|
str blob; |
|
|
|
str_init_len(&blob, row[0], lengths[0]); |
|
|
|
int ret = media_player_play_blob(mp, &blob, repeat, start_pos); |
|
|
|
int ret = media_player_play_blob_id(mp, &blob, repeat, start_pos, id); |
|
|
|
|
|
|
|
mysql_free_result(res); |
|
|
|
|
|
|
|
@ -786,11 +1142,71 @@ static void media_player_run(void *ptr) { |
|
|
|
|
|
|
|
log_info_pop(); |
|
|
|
} |
|
|
|
|
|
|
|
static unsigned int media_player_cache_entry_hash(const void *p) { |
|
|
|
const struct media_player_cache_index *i = p; |
|
|
|
unsigned int ret; |
|
|
|
switch (i->index.type) { |
|
|
|
case MP_DB: |
|
|
|
ret = i->index.db_id; |
|
|
|
break; |
|
|
|
case MP_FILE: |
|
|
|
case MP_BLOB: |
|
|
|
ret = str_hash(&i->index.file); |
|
|
|
break; |
|
|
|
default: |
|
|
|
abort(); |
|
|
|
} |
|
|
|
ret ^= str_hash(&i->dst_pt.encoding_with_full_params); |
|
|
|
ret ^= i->index.type; |
|
|
|
return ret; |
|
|
|
} |
|
|
|
static gboolean media_player_cache_entry_eq(const void *A, const void *B) { |
|
|
|
const struct media_player_cache_index *a = A, *b = B; |
|
|
|
if (a->index.type != b->index.type) |
|
|
|
return FALSE; |
|
|
|
switch (a->index.type) { |
|
|
|
case MP_DB: |
|
|
|
if (a->index.db_id != b->index.db_id) |
|
|
|
return FALSE; |
|
|
|
break; |
|
|
|
case MP_FILE: |
|
|
|
case MP_BLOB: |
|
|
|
if (!str_equal(&a->index.file, &b->index.file)) |
|
|
|
return FALSE; |
|
|
|
break; |
|
|
|
default: |
|
|
|
abort(); |
|
|
|
} |
|
|
|
return str_equal(&a->dst_pt.encoding_with_full_params, &b->dst_pt.encoding_with_full_params); |
|
|
|
} |
|
|
|
static void media_player_cache_index_free(void *p) { |
|
|
|
struct media_player_cache_index *i = p; |
|
|
|
g_free(i->index.file.s); |
|
|
|
payload_type_clear(&i->dst_pt); |
|
|
|
g_slice_free1(sizeof(*i), i); |
|
|
|
} |
|
|
|
static void media_player_cache_entry_free(void *p) { |
|
|
|
struct media_player_cache_entry *e = p; |
|
|
|
g_ptr_array_free(e->packets, TRUE); |
|
|
|
mutex_destroy(&e->lock); |
|
|
|
g_free(e->info_str); |
|
|
|
media_player_coder_shutdown(&e->coder); |
|
|
|
av_packet_free(&e->coder.pkt); |
|
|
|
g_slice_free1(sizeof(*e), e); |
|
|
|
} |
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
void media_player_init(void) { |
|
|
|
#ifdef WITH_TRANSCODING |
|
|
|
if (rtpe_config.player_cache) { |
|
|
|
media_player_cache = g_hash_table_new_full(media_player_cache_entry_hash, |
|
|
|
media_player_cache_entry_eq, media_player_cache_index_free, |
|
|
|
media_player_cache_entry_free); |
|
|
|
mutex_init(&media_player_cache_lock); |
|
|
|
} |
|
|
|
|
|
|
|
timerthread_init(&media_player_thread, media_player_run); |
|
|
|
#endif |
|
|
|
timerthread_init(&send_timer_thread, timerthread_queue_run); |
|
|
|
@ -799,6 +1215,11 @@ void media_player_init(void) { |
|
|
|
void media_player_free(void) { |
|
|
|
#ifdef WITH_TRANSCODING |
|
|
|
timerthread_free(&media_player_thread); |
|
|
|
|
|
|
|
if (media_player_cache) { |
|
|
|
mutex_destroy(&media_player_cache_lock); |
|
|
|
g_hash_table_destroy(media_player_cache); |
|
|
|
} |
|
|
|
#endif |
|
|
|
timerthread_free(&send_timer_thread); |
|
|
|
} |
|
|
|
|