You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
312 lines
9.8 KiB
312 lines
9.8 KiB
/**
|
|
* @file pcm.c
|
|
* @brief Real-Time Protocol (RTP) sample-based audio
|
|
* This files defines the same-based RTP payload formats (mostly from RFC3551)
|
|
* including linear, logarithmic and adaptive audio formats.
|
|
*/
|
|
/*****************************************************************************
|
|
* Copyright © 2021 Rémi Denis-Courmont
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public License
|
|
* as published by the Free Software Foundation; either version 2.1
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
****************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
#include <assert.h>
|
|
#include <stdbit.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <vlc_common.h>
|
|
#include <vlc_aout.h>
|
|
#include <vlc_block.h>
|
|
#include <vlc_es.h>
|
|
#include <vlc_plugin.h>
|
|
#include <vlc_strings.h>
|
|
|
|
#include "rtp.h"
|
|
#define RTP_MAX_CHANS 6
|
|
|
|
struct rtp_pcm {
|
|
vlc_fourcc_t fourcc;
|
|
uint16_t channel_mask;
|
|
uint8_t sample_bits;
|
|
uint8_t channel_count;
|
|
bool channel_reorder;
|
|
uint8_t channel_map[RTP_MAX_CHANS];
|
|
};
|
|
|
|
static void *rtp_pcm_init(struct vlc_rtp_pt *pt)
|
|
{
|
|
struct rtp_pcm *sys = pt->opaque;
|
|
es_format_t fmt;
|
|
|
|
es_format_Init(&fmt, AUDIO_ES, sys->fourcc);
|
|
fmt.audio.i_rate = pt->frequency;
|
|
fmt.audio.i_physical_channels = sys->channel_mask;
|
|
fmt.audio.i_channels = sys->channel_count;
|
|
aout_FormatPrepare(&fmt.audio);
|
|
return vlc_rtp_pt_request_es(pt, &fmt);
|
|
}
|
|
|
|
static void rtp_pcm_destroy(struct vlc_rtp_pt *pt, void *data)
|
|
{
|
|
struct vlc_rtp_es *es = data;
|
|
|
|
vlc_rtp_es_destroy(es);
|
|
(void) pt;
|
|
}
|
|
|
|
static void rtp_pcm_reorder(void *restrict out, const void *restrict in,
|
|
size_t frames, size_t sample_size,
|
|
size_t channels, const uint8_t *restrict map)
|
|
{
|
|
unsigned char *outp = out;
|
|
const unsigned char *inp = in;
|
|
const size_t frame_size = sample_size * channels;
|
|
|
|
if (sample_size == 0 || sample_size > 3)
|
|
vlc_assert_unreachable(); /* Let compiler optimise the memcpy(). */
|
|
|
|
for (size_t i = 0; i < frames; i++) {
|
|
for (size_t j = 0; j < channels; j++) {
|
|
memcpy(outp + (sample_size * map[j]), inp, sample_size);
|
|
inp += sample_size;
|
|
}
|
|
|
|
outp += frame_size;
|
|
}
|
|
}
|
|
|
|
static void rtp_pcm_decode(struct vlc_rtp_pt *pt, void *data, block_t *block,
|
|
const struct vlc_rtp_pktinfo *restrict info)
|
|
{
|
|
struct rtp_pcm *sys = pt->opaque;
|
|
struct vlc_rtp_es *es = data;
|
|
const size_t frame_bits = sys->channel_count * sys->sample_bits;
|
|
size_t frames = (8 * block->i_buffer) / frame_bits;
|
|
|
|
block->i_buffer = ((frames * frame_bits) + 7) / 8;
|
|
block->i_dts = VLC_TICK_INVALID;
|
|
|
|
if (info->m)
|
|
block->i_flags |= BLOCK_FLAG_DISCONTINUITY;
|
|
|
|
if (sys->channel_reorder) {
|
|
block_t *reordered = block_Alloc(block->i_buffer);
|
|
|
|
assert((sys->sample_bits % 8) == 0);
|
|
|
|
if (likely(reordered != NULL)) {
|
|
block_CopyProperties(reordered, block);
|
|
rtp_pcm_reorder(reordered->p_buffer, block->p_buffer, frames,
|
|
sys->sample_bits / 8, sys->channel_count,
|
|
sys->channel_map);
|
|
|
|
}
|
|
|
|
block_Release(block);
|
|
block = reordered;
|
|
|
|
if (unlikely(block == NULL))
|
|
return;
|
|
}
|
|
|
|
vlc_rtp_es_send(es, block);
|
|
}
|
|
|
|
static void rtp_pcm_release(struct vlc_rtp_pt *pt)
|
|
{
|
|
struct rtp_pcm *sys = pt->opaque;
|
|
|
|
free(sys);
|
|
}
|
|
|
|
static const struct vlc_rtp_pt_operations rtp_pcm_ops = {
|
|
rtp_pcm_release, rtp_pcm_init, rtp_pcm_destroy, rtp_pcm_decode,
|
|
};
|
|
|
|
|
|
static void *rtp_g722_init(struct vlc_rtp_pt *pt)
|
|
{
|
|
struct rtp_pcm *sys = pt->opaque;
|
|
es_format_t fmt;
|
|
|
|
es_format_Init(&fmt, AUDIO_ES, VLC_CODEC_ADPCM_G722);
|
|
/* For historical reasons, the RTP clock rate is 8000 Hz
|
|
* but the sampling rate really is 16000 Hz. */
|
|
fmt.audio.i_rate = 16000;
|
|
fmt.audio.i_physical_channels = sys->channel_mask;
|
|
fmt.audio.i_channels = sys->channel_count;
|
|
aout_FormatPrepare(&fmt.audio);
|
|
return vlc_rtp_pt_request_es(pt, &fmt);
|
|
}
|
|
|
|
static const struct vlc_rtp_pt_operations rtp_g722_ops = {
|
|
rtp_pcm_release, rtp_g722_init, rtp_pcm_destroy, rtp_pcm_decode,
|
|
};
|
|
|
|
|
|
static void *rtp_g726_init(struct vlc_rtp_pt *pt)
|
|
{
|
|
struct rtp_pcm *sys = pt->opaque;
|
|
es_format_t fmt;
|
|
|
|
es_format_Init(&fmt, AUDIO_ES, sys->fourcc);
|
|
fmt.audio.i_rate = 8000;
|
|
fmt.audio.i_physical_channels = sys->channel_mask;
|
|
fmt.audio.i_channels = sys->channel_count;
|
|
/* By VLC convention, the decoder knows the bit depth from the bit rate. */
|
|
fmt.i_bitrate = sys->sample_bits * sys->channel_count * 8000;
|
|
aout_FormatPrepare(&fmt.audio);
|
|
return vlc_rtp_pt_request_es(pt, &fmt);
|
|
}
|
|
|
|
static const struct vlc_rtp_pt_operations rtp_g726_ops = {
|
|
rtp_pcm_release, rtp_g726_init, rtp_pcm_destroy, rtp_pcm_decode,
|
|
};
|
|
|
|
|
|
static const uint32_t channel_masks[] = {
|
|
/* By default, there is only one channel. */
|
|
AOUT_CHAN_CENTER,
|
|
/*
|
|
* RTP/AVP recommends AIFF-C channel order by default (RFC3551 §4.1).
|
|
* For 1-4 channel(s), this works well.
|
|
*/
|
|
AOUT_CHAN_CENTER, AOUT_CHANS_2_0, AOUT_CHANS_3_0, AOUT_CHANS_3_1,
|
|
/* AIFF-C says for 5 channels, and RFC says 3.2. We assume normal 5.0. */
|
|
AOUT_CHANS_5_0,
|
|
/* Accordingly for 6 channels, we assume normal 5.1 instead of AIFF-C's. */
|
|
AOUT_CHANS_5_1,
|
|
};
|
|
|
|
static const uint32_t channel_order[] = {
|
|
AOUT_CHAN_LEFT, AOUT_CHAN_REARLEFT, AOUT_CHAN_CENTER,
|
|
AOUT_CHAN_RIGHT, AOUT_CHAN_REARRIGHT, AOUT_CHAN_LFE, 0,
|
|
};
|
|
|
|
/* RTP puts right before center for 3.0, but center before right for 3.1! */
|
|
static const uint32_t channel_order_3[RTP_MAX_CHANS] = {
|
|
AOUT_CHAN_LEFT, AOUT_CHAN_RIGHT, AOUT_CHAN_CENTER,
|
|
};
|
|
|
|
static_assert (ARRAY_SIZE(channel_masks) == RTP_MAX_CHANS + 1, "Bad masks");
|
|
static_assert (ARRAY_SIZE(channel_order) == RTP_MAX_CHANS + 1, "Bad order");
|
|
|
|
static int rtp_pcm_open(vlc_object_t *obj, struct vlc_rtp_pt *pt,
|
|
const struct vlc_sdp_pt *desc)
|
|
{
|
|
vlc_fourcc_t fourcc;
|
|
unsigned bits;
|
|
const struct vlc_rtp_pt_operations *ops = &rtp_pcm_ops;
|
|
|
|
if (vlc_ascii_strcasecmp(desc->name, "L8") == 0) {
|
|
fourcc = VLC_CODEC_U8; /* RFC3551 §4.5.10 */
|
|
bits = 8;
|
|
|
|
} else if (vlc_ascii_strcasecmp(desc->name, "L16") == 0) {
|
|
fourcc = VLC_CODEC_S16B; /* RFC3551 §4.5.11 */
|
|
bits = 16;
|
|
|
|
} else if (vlc_ascii_strcasecmp(desc->name, "L20") == 0) {
|
|
fourcc = VLC_CODEC_S20B; /* RFC3190 §4 */
|
|
bits = 20;
|
|
|
|
} else if (vlc_ascii_strcasecmp(desc->name, "L24") == 0) {
|
|
fourcc = VLC_CODEC_S24B; /* RFC3190 §4 */
|
|
bits = 24;
|
|
|
|
} else if (vlc_ascii_strcasecmp(desc->name, "PCMA") == 0) {
|
|
fourcc = VLC_CODEC_ALAW; /* RFC3551 §4.5.14 */
|
|
bits = 8;
|
|
|
|
} else if (vlc_ascii_strcasecmp(desc->name, "PCMU") == 0) {
|
|
fourcc = VLC_CODEC_MULAW; /* RFC3551 §4.5.14 */
|
|
bits = 8;
|
|
|
|
} else if (vlc_ascii_strcasecmp(desc->name, "G722") == 0) {
|
|
fourcc = VLC_CODEC_ADPCM_G722; /* RFC3551 §4.5.2 */
|
|
bits = 8;
|
|
ops = &rtp_g722_ops;
|
|
|
|
} else if (sscanf(desc->name, "G726-%u", &bits) == 1
|
|
|| sscanf(desc->name, "g726-%u", &bits) == 1) {
|
|
fourcc = VLC_CODEC_ADPCM_G726_LE; /* RFC3551 §4.5.4 */
|
|
bits /= 8;
|
|
ops = &rtp_g726_ops;
|
|
|
|
if (unlikely(bits < 16 || bits % 8 || bits > 40))
|
|
return VLC_ENOTSUP;
|
|
|
|
} else if (vlc_ascii_strcasecmp(desc->name, "DAT12") == 0) {
|
|
fourcc = VLC_CODEC_DAT12; /* RFC3190 §3 */
|
|
bits = 12;
|
|
|
|
} else if (vlc_ascii_strcasecmp(desc->name, "32kadpcm") == 0) {
|
|
fourcc = VLC_CODEC_ADPCM_G726; /* RFC2422 */
|
|
bits = 4;
|
|
ops = &rtp_g726_ops;
|
|
|
|
} else
|
|
return VLC_ENOTSUP;
|
|
|
|
struct rtp_pcm *sys = malloc(sizeof (*sys));
|
|
if (unlikely(sys == NULL))
|
|
return VLC_ENOMEM;
|
|
|
|
sys->fourcc = fourcc;
|
|
sys->channel_count = desc->channel_count ? desc->channel_count : 1;
|
|
sys->channel_reorder = false;
|
|
sys->sample_bits = bits;
|
|
|
|
if (desc->channel_count < ARRAY_SIZE(channel_masks)) {
|
|
sys->channel_mask = channel_masks[desc->channel_count];
|
|
assert(stdc_count_ones(sys->channel_mask) == sys->channel_count);
|
|
|
|
/* Octet-unaligned formats cannot readily be reordered, especially in
|
|
* foreign endianness. The decoder will take care of that. */
|
|
if ((bits % 8) == 0) {
|
|
const uint32_t *order = channel_order;
|
|
|
|
if (desc->channel_count == 3)
|
|
order = channel_order_3;
|
|
|
|
if (aout_CheckChannelReorder(order, NULL, sys->channel_mask,
|
|
sys->channel_map))
|
|
sys->channel_reorder = true;
|
|
}
|
|
} else {
|
|
msg_Warn(obj, "unknown %hhu-channels layout", desc->channel_count);
|
|
sys->channel_mask = 0;
|
|
}
|
|
|
|
pt->opaque = sys;
|
|
pt->ops = ops;
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
vlc_module_begin()
|
|
set_shortname(N_("RTP PCM"))
|
|
set_description(N_("RTP PCM payload parser"))
|
|
set_subcategory(SUBCAT_INPUT_DEMUX)
|
|
set_rtp_parser_callback(rtp_pcm_open)
|
|
add_shortcut("audio/L8", "audio/L16", "audio/L20", "audio/L24",
|
|
"audio/DAT12", "audio/PCMA", "audio/PCMU", "audio/G722",
|
|
"audio/G726-16", "audio/G726-24", "audio/G726-32",
|
|
"audio/G726-40", "audio/32kadpcm")
|
|
/* TODO? DVI4, VDVI */
|
|
vlc_module_end()
|
|
|