diff --git a/daemon/codec.c b/daemon/codec.c index 3d130d1f9..6c09d94cb 100644 --- a/daemon/codec.c +++ b/daemon/codec.c @@ -78,6 +78,7 @@ struct transcode_packet { static codec_handler_func handler_func_passthrough_ssrc; static codec_handler_func handler_func_transcode; +static codec_handler_func handler_func_playback; static codec_handler_func handler_func_dtmf; static struct ssrc_entry *__ssrc_handler_transcode_new(void *p); @@ -97,6 +98,9 @@ static struct codec_handler codec_handler_stub_ssrc = { static void __handler_shutdown(struct codec_handler *handler) { free_ssrc_hash(&handler->ssrc_hash); + if (handler->ssrc_handler) + obj_put(&handler->ssrc_handler->h); + handler->ssrc_handler = NULL; handler->kernelize = 0; handler->transcoder = 0; } @@ -106,6 +110,9 @@ static void __codec_handler_free(void *pp) { __handler_shutdown(h); g_slice_free1(sizeof(*h), h); } +void codec_handler_free(struct codec_handler *handler) { + __codec_handler_free(handler); +} static struct codec_handler *__handler_new(struct rtp_payload_type *pt) { struct codec_handler *handler = g_slice_alloc0(sizeof(*handler)); @@ -173,6 +180,21 @@ reset: STR_FMT(&dest->encoding_with_params)); } +struct codec_handler *codec_handler_make_playback(struct rtp_payload_type *src_pt, + struct rtp_payload_type *dst_pt) +{ + struct codec_handler *handler = __handler_new(src_pt); + handler->dest_pt = *dst_pt; + handler->func = handler_func_playback; + handler->ssrc_handler = (void *) __ssrc_handler_transcode_new(handler); + + ilog(LOG_DEBUG, "Created media playback context for " STR_FORMAT " -> " STR_FORMAT "", + STR_FMT(&src_pt->encoding_with_params), + STR_FMT(&dst_pt->encoding_with_params)); + + return handler; +} + static void __ensure_codec_def(struct rtp_payload_type *pt, struct call_media *media) { if (pt->codec_def) return; @@ -1075,6 +1097,12 @@ static int handler_func_transcode(struct codec_handler *h, struct media_packet * return __handler_func_sequencer(h, mp, packet); } +static int handler_func_playback(struct codec_handler *h, struct media_packet *mp) { + decoder_input_data(h->ssrc_handler->decoder, &mp->payload, mp->rtp->timestamp, + __packet_decoded, h->ssrc_handler, mp); + return 0; +} + diff --git a/daemon/media_player.c b/daemon/media_player.c index dcfb03c79..f901180be 100644 --- a/daemon/media_player.c +++ b/daemon/media_player.c @@ -7,6 +7,10 @@ #include "timerthread.h" #include "call.h" #include "str.h" +#include "rtplib.h" +#include "codec.h" +#include "media_socket.h" +#include "ssrc.h" @@ -20,6 +24,13 @@ static void media_player_shutdown(struct media_player *mp) { timerthread_obj_deschedule(&mp->tt_obj); avformat_free_context(mp->fmtctx); mp->fmtctx = NULL; + mp->media = NULL; + if (mp->handler) + codec_handler_free(mp->handler); + mp->handler = NULL; + if (mp->ssrc_out) + obj_put(&mp->ssrc_out->parent->h); + mp->ssrc_out = NULL; } @@ -49,6 +60,7 @@ struct media_player *media_player_new(struct call_monologue *ml) { mutex_init(&mp->lock); mp->call = obj_get(ml->call); mp->ml = ml; + mp->seq = random(); av_init_packet(&mp->pkt); mp->pkt.data = NULL; @@ -58,6 +70,50 @@ struct media_player *media_player_new(struct call_monologue *ml) { } +static int __ensure_codec_handler(struct media_player *mp, AVStream *avs) { + if (mp->handler) + return 0; + + // synthesise rtp payload type + struct rtp_payload_type src_pt = { .payload_type = -1 }; + // src_pt.codec_def = codec_find_by_av(avs->codec->codec_id); `codec` is deprecated + src_pt.codec_def = codec_find_by_av(avs->codecpar->codec_id); + if (!src_pt.codec_def) { + ilog(LOG_ERR, "Attempting to play media from an unsupported file format/codec"); + return -1; + } + src_pt.encoding = src_pt.codec_def->rtpname_str; + src_pt.channels = avs->codecpar->channels; + src_pt.clock_rate = avs->codecpar->sample_rate; + codec_init_payload_type(&src_pt, mp->media); + + // find suitable output payload type + struct rtp_payload_type *dst_pt; + for (GList *l = mp->media->codecs_prefs_send.head; l; l = l->next) { + dst_pt = l->data; + if (dst_pt->codec_def && !dst_pt->codec_def->pseudocodec) + goto found; + } + dst_pt = NULL; +found: + if (!dst_pt) { + ilog(LOG_ERR, "No supported output codec found in SDP"); + return -1; + } + ilog(LOG_DEBUG, "Output codec for media playback is " STR_FORMAT, + STR_FMT(&dst_pt->encoding_with_params)); + + mp->handler = codec_handler_make_playback(&src_pt, dst_pt); + if (!mp->handler) + return -1; + mp->ssrc_out = get_ssrc_ctx(random(), mp->call->ssrc_hash, SSRC_DIR_OUTPUT); + if (!mp->ssrc_out) + return -1; + + return 0; +} + + // appropriate lock must be held static void media_player_read_packet(struct media_player *mp) { int ret = av_read_frame(mp->fmtctx, &mp->pkt); @@ -81,11 +137,50 @@ static void media_player_read_packet(struct media_player *mp) { goto out; } + if (__ensure_codec_handler(mp, avs)) + goto out; + + // scale pts and duration according to sample rate + + long long duration_scaled = mp->pkt.duration * avs->codecpar->sample_rate + * avs->time_base.num / avs->time_base.den; + unsigned long long pts_scaled = mp->pkt.pts * avs->codecpar->sample_rate + * avs->time_base.num / avs->time_base.den; + long long us_dur = mp->pkt.duration * 1000000LL * avs->time_base.num / avs->time_base.den; - ilog(LOG_DEBUG, "read media packet: duration %llu (%lli us), time_base %i/%i", - (unsigned long long) mp->pkt.duration, us_dur, + ilog(LOG_DEBUG, "read media packet: pts %llu duration %lli (scaled %llu/%lli, %lli us), " + "sample rate %i, time_base %i/%i", + (unsigned long long) mp->pkt.pts, + (long long) mp->pkt.duration, + pts_scaled, + duration_scaled, + us_dur, + avs->codecpar->sample_rate, avs->time_base.num, avs->time_base.den); + // synthesise fake RTP header and media_packet context + + struct rtp_header rtp = { + .timestamp = pts_scaled, // taken verbatim by handler_func_playback w/o byte swap + .seq_num = htons(mp->seq++), + }; + struct media_packet packet = { + .tv = rtpe_now, + .call = mp->call, + .media = mp->media, + .rtp = &rtp, + .ssrc_out = mp->ssrc_out, + }; + str_init_len(&packet.raw, (char *) mp->pkt.data, mp->pkt.size); + packet.payload = packet.raw; + + mp->handler->func(mp->handler, &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); + timeval_add_usec(&mp->next_run, us_dur); timerthread_obj_schedule_abs(&mp->tt_obj, &mp->next_run); @@ -98,6 +193,27 @@ out: int media_player_play_file(struct media_player *mp, const str *file) { media_player_shutdown(mp); + // find call media suitable for playback + struct call_media *media; + for (GList *l = mp->ml->medias.head; l; l = l->next) { + media = l->data; + if (media->type_id != MT_AUDIO) + continue; + if (!MEDIA_ISSET(media, SEND)) + continue; + if (media->streams.length == 0) + continue; + goto found; + } + media = NULL; +found: + if (!media) { + ilog(LOG_ERR, "No suitable SDP section for media playback"); + return -1; + } + mp->media = media; + mp->sink = media->streams.head->data; + char file_s[PATH_MAX]; snprintf(file_s, sizeof(file_s), STR_FORMAT, STR_FMT(file)); diff --git a/include/codec.h b/include/codec.h index 19f7d7a0e..7d4b6887a 100644 --- a/include/codec.h +++ b/include/codec.h @@ -14,6 +14,7 @@ struct codec_handler; struct media_packet; struct ssrc_hash; struct sdp_ng_flags; +struct codec_ssrc_handler; typedef int codec_handler_func(struct codec_handler *, struct media_packet *); @@ -27,6 +28,9 @@ struct codec_handler { int transcoder:1; struct ssrc_hash *ssrc_hash; + + // for media playback + struct codec_ssrc_handler *ssrc_handler; }; struct codec_packet { @@ -37,6 +41,9 @@ struct codec_packet { struct codec_handler *codec_handler_get(struct call_media *, int payload_type); void codec_handlers_free(struct call_media *); +struct codec_handler *codec_handler_make_playback(struct rtp_payload_type *src_pt, + struct rtp_payload_type *dst_pt); +void codec_handler_free(struct codec_handler *handler); void codec_add_raw_packet(struct media_packet *mp); void codec_packet_free(void *); diff --git a/include/media_player.h b/include/media_player.h index e77aee420..742b9b902 100644 --- a/include/media_player.h +++ b/include/media_player.h @@ -12,6 +12,9 @@ struct call; struct call_monologue; +struct codec_handler; +struct ssrc_ctx; +struct packet_stream; struct media_player { @@ -19,11 +22,16 @@ struct media_player { mutex_t lock; struct call *call; struct call_monologue *ml; + struct call_media *media; + struct packet_stream *sink; struct timeval next_run; AVFormatContext *fmtctx; AVPacket pkt; + struct codec_handler *handler; + struct ssrc_ctx *ssrc_out; + unsigned long seq; }; diff --git a/lib/codeclib.c b/lib/codeclib.c index 934e54c50..0422947d3 100644 --- a/lib/codeclib.c +++ b/lib/codeclib.c @@ -379,6 +379,16 @@ const codec_def_t *codec_find(const str *name, enum media_type type) { return ret; } +const codec_def_t *codec_find_by_av(enum AVCodecID id) { + // XXX hash this + for (int i = 0; i < G_N_ELEMENTS(__codec_defs); i++) { + codec_def_t *def = &__codec_defs[i]; + if (def->avcodec_id == id) + return def; + } + return NULL; +} + enum media_type codec_get_type(const str *type) { if (!type || !type->len) return MT_UNKNOWN; diff --git a/lib/codeclib.h b/lib/codeclib.h index 5d2f93e41..f6fc640c2 100644 --- a/lib/codeclib.h +++ b/lib/codeclib.h @@ -191,6 +191,7 @@ void codeclib_init(int); const codec_def_t *codec_find(const str *name, enum media_type); +const codec_def_t *codec_find_by_av(enum AVCodecID); enum media_type codec_get_type(const str *type);