diff --git a/modules/access/rtp/mpeg12.c b/modules/access/rtp/mpeg12.c index e26d7ca4532cdd9000a6cdb07ee75afa8e698db2..ba35af4e9d8e534a06df372928d817a7aff53de2 100644 --- a/modules/access/rtp/mpeg12.c +++ b/modules/access/rtp/mpeg12.c @@ -34,8 +34,12 @@ #include <vlc_strings.h> #include "rtp.h" +#include "../../packetizer/mpegaudio.h" +/* audio/MPA: MPEG-1/2 Audio layer I/II/III ES */ struct rtp_mpa { + block_t *frags; + block_t **frag_end; size_t offset; struct vlc_rtp_es *es; }; @@ -46,14 +50,32 @@ static void *rtp_mpa_init(struct vlc_rtp_pt *pt) if (unlikely(sys == NULL)) return NULL; + sys->frags = NULL; + sys->frag_end = &sys->frags; + sys->offset = 0; + es_format_t fmt; es_format_Init(&fmt, AUDIO_ES, VLC_CODEC_MPGA); - fmt.b_packetized = false; sys->es = vlc_rtp_pt_request_es(pt, &fmt); return sys; } +static void rtp_mpa_send(struct rtp_mpa *sys) +{ + block_t *frags = sys->frags; + block_t *frame = block_ChainGather(frags); + + if (likely(frame != NULL)) + vlc_rtp_es_send(sys->es, frame); + else + block_ChainRelease(frags); + + sys->frags = NULL; + sys->frag_end = &sys->frags; + sys->offset = 0; +} + static void rtp_mpa_destroy(struct vlc_rtp_pt *pt, void *data) { struct rtp_mpa *sys = data; @@ -61,6 +83,8 @@ static void rtp_mpa_destroy(struct vlc_rtp_pt *pt, void *data) if (unlikely(sys == NULL)) return; + if (sys->frags != NULL) + rtp_mpa_send(sys); vlc_rtp_es_destroy(sys->es); free(sys); (void) pt; @@ -84,19 +108,74 @@ static void rtp_mpa_decode(struct vlc_rtp_pt *pt, void *data, block_t *block) block->p_buffer += 4; block->i_buffer -= 4; - if (offset != 0 && offset != sys->offset) { + if (offset < sys->offset) + rtp_mpa_send(sys); /* New frame started: send pending frame if any */ + + if (offset != sys->offset) { /* Discontinuous offset, probably packet loss. */ vlc_warning(log, "offset discontinuity: expected %zu, got %"PRIuFAST16, sys->offset, offset); - block->i_flags |= BLOCK_FLAG_DISCONTINUITY | BLOCK_FLAG_CORRUPTED; + block->i_flags |= BLOCK_FLAG_CORRUPTED; } + if (block->i_buffer == 0) + goto drop; + + *sys->frag_end = block; + sys->frag_end = &block->p_next; sys->offset = offset + block->i_buffer; - /* This payload format does not flag the last fragment of a frame, - * so the MPGA header must be parsed to figure out the frame size and end - * (without introducing a one-packet delay). - * We let the packetiser handle it rather than duplicate the logic. */ - vlc_rtp_es_send(sys->es, block); + block = sys->frags; + + /* Extract full MPEG Audio frames */ + for (;;) { + uint32_t header; + + if (block_ChainExtract(block, &header, 4) < 4) + break; + + /* This RTP payload format does atypically not flag end-of-frames, so + * we have to parse the MPEG Audio frame header to find out. */ + unsigned chans, conf, mode, srate, brate, samples, maxsize, layer; + int frame_size = SyncInfo(ntoh32(header), &chans, &conf, &mode, &srate, + &brate, &samples, &maxsize, &layer); + /* If the frame size is unknown due to free bit rate, then we can only + * sense the completion of the frame when the next frame starts. */ + if (frame_size <= 0) + break; + + if ((size_t)frame_size == sys->offset) { + rtp_mpa_send(sys); + break; + } + + /* If the frame size is larger than the current offset, then we need to + * wait for the next fragment. */ + if ((size_t)frame_size > sys->offset) + break; + + if (offset != 0) { + vlc_warning(log, "invalid frame fragmentation"); + block->i_flags |= BLOCK_FLAG_CORRUPTED; + break; + } + + /* The RTP packet contains multiple small frames. */ + block_t *frame = block_Alloc(frame_size); + if (likely(frame != NULL)) { + assert(block->p_next == NULL); /* Only one block to copy from */ + memcpy(frame->p_buffer, block->p_buffer, frame_size); + frame->i_flags = block->i_flags; + frame->i_pts = block->i_pts; + vlc_rtp_es_send(sys->es, frame); + + block->i_flags = 0; + } else + block->i_flags = BLOCK_FLAG_DISCONTINUITY; + + block->p_buffer += frame_size; + block->i_buffer -= frame_size; + block->i_pts = VLC_TICK_INVALID; + } return; drop: @@ -112,7 +191,7 @@ static int rtp_mpeg12_open(vlc_object_t *obj, struct vlc_rtp_pt *pt, { pt->opaque = vlc_object_logger(obj); - if (vlc_ascii_strcasecmp(desc->name, "MPA") == 0) /* RFC2250 */ + if (vlc_ascii_strcasecmp(desc->name, "MPA") == 0) /* RFC2250 §3.2 */ pt->ops = &rtp_mpa_ops; else return VLC_ENOTSUP;