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.
 
 
 
 
 
 

391 lines
12 KiB

/**
* @file mpeg4.c
*/
/*****************************************************************************
* Copyright (C) 2024 VideoLabs, VLC authors and VideoLAN
*
* 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 "h26x.h"
#include "fmtp.h"
#include "../../packetizer/mpeg4audio.h"
#include "../../packetizer/mpeg4systems.h"
#include <vlc_plugin.h>
#include <vlc_codec.h>
#include <vlc_bits.h>
struct mpeg4_pt_opaque
{
// RFC 3640
uint8_t streamType;
uint8_t sizeLength;
uint8_t indexLength;
uint8_t indexDeltaLength;
uint8_t constantSize;
uint8_t CTSDeltaLength;
uint8_t DTSDeltaLength;
uint8_t randomAccessIndication;
uint8_t auxiliaryDataSizeLength;
// RFC 6416
uint8_t object_id;
// unsigned profile_level_id;
block_t *config;
vlc_object_t *obj;
};
static void *rtp_mpeg4_init(struct vlc_rtp_pt *pt, es_format_t *fmt)
{
struct mpeg4_pt_opaque *opaque = pt->opaque;
block_t *config = opaque->config;
enum es_format_category_e es_cat = fmt->i_cat;
struct rtp_h26x_sys *sys = malloc(sizeof(*sys));
if(!sys)
return NULL;
rtp_h26x_init(sys);
sys->p_packetizer = demux_PacketizerNew(opaque->obj, fmt, "rtp packetizer");
if(!sys->p_packetizer)
{
free(sys);
return NULL;
}
sys->es = vlc_rtp_pt_request_es(pt, &sys->p_packetizer->fmt_out);
if(config && es_cat == VIDEO_ES)
sys->xps = block_Duplicate(config);
return sys;
}
static void *rtp_mpeg4v_init(struct vlc_rtp_pt *pt)
{
es_format_t fmt;
es_format_Init (&fmt, VIDEO_ES, VLC_CODEC_MP4V);
fmt.b_packetized = false;
return rtp_mpeg4_init(pt, &fmt);
}
static vlc_fourcc_t audioObjectTypeToCodec(uint8_t objectType)
{
switch(objectType)
{
case MPEG4_AOT_AAC_MAIN:
case MPEG4_AOT_AAC_LC:
case MPEG4_AOT_AAC_SSR:
case MPEG4_AOT_AAC_LTP:
case MPEG4_AOT_AAC_SBR:
case MPEG4_AOT_AAC_SC:
case MPEG4_AOT_SSC:
case MPEG4_AOT_AAC_PS:
case MPEG4_AOT_ER_AAC_LC:
case MPEG4_AOT_ER_AAC_LTP:
case MPEG4_AOT_ER_AAC_SC:
case MPEG4_AOT_ER_BSAC:
case MPEG4_AOT_ER_AAC_LD:
case MPEG4_AOT_ER_AAC_ELD:
return VLC_CODEC_MP4A;
case MPEG4_AOT_CELP:
case MPEG4_AOT_ER_CELP:
return VLC_CODEC_QCELP;
default:
return 0;
}
}
static void *rtp_mpeg4a_init(struct vlc_rtp_pt *pt)
{
struct mpeg4_pt_opaque *opaque = pt->opaque;
es_format_t fmt;
es_format_Init(&fmt, AUDIO_ES, 0);
fmt.b_packetized = false;
if(!opaque->sizeLength && !opaque->constantSize) // RFC 6416 is MP4A-LATM
{
fmt.i_codec = audioObjectTypeToCodec(opaque->object_id);
fmt.i_original_fourcc = VLC_FOURCC('L','A','T','M');
}
else // RFC 3640 MPEG4-GENERIC
{
const block_t *config = opaque->config;
if(config)
{
bs_t bs;
bs_init(&bs, config->p_buffer, config->i_buffer);
MPEG4_asc_t cfg = {0};
if(MPEG4_read_AudioSpecificConfig(&bs, &cfg, false) == VLC_SUCCESS)
{
fmt.i_codec = audioObjectTypeToCodec(cfg.i_object_type);
fmt.audio.i_rate = cfg.i_samplerate;
}
else fmt.i_codec = VLC_CODEC_MP4A;
}
else
{
fmt.i_codec = VLC_CODEC_MP4A;
}
}
return rtp_mpeg4_init(pt, &fmt);
}
static void rtp_mpeg4_destroy(struct vlc_rtp_pt *pt, void *data)
{
VLC_UNUSED(pt);
struct rtp_h26x_sys *sys = data;
if(sys)
{
if(sys->p_packetizer)
demux_PacketizerDestroy(sys->p_packetizer);
vlc_rtp_es_destroy(sys->es);
rtp_h26x_clear(sys);
free(sys);
}
}
static void rtp_mpeg4_decode(struct vlc_rtp_pt *pt, void *data, block_t *block,
const struct vlc_rtp_pktinfo *restrict info)
{
struct mpeg4_pt_opaque *opaque = pt->opaque;
struct rtp_h26x_sys *sys = data;
if(opaque->sizeLength && block->i_buffer > 2)
{
uint32_t auIndex = (uint32_t) -1;
uint16_t auHeadersLength = GetWBE(block->p_buffer);
size_t data_section_offset = 2 + (auHeadersLength + 7) / 8;
if(data_section_offset + 2 >= block->i_buffer)
goto drop;
/* AUX header parsing */
if(opaque->auxiliaryDataSizeLength)
{
const uint8_t *auxheader_data = &block->p_buffer[data_section_offset];
size_t auxheader_data_size = block->i_buffer - data_section_offset;
bs_t bs;
bs_init(&bs, auxheader_data, auxheader_data_size);
uint32_t auxSize = bs_read(&bs, opaque->auxiliaryDataSizeLength);
data_section_offset += (auxheader_data_size + auxSize + 7) / 8;
if(data_section_offset >= block->i_buffer)
goto drop;
}
const uint8_t *data_section = &block->p_buffer[data_section_offset];
size_t data_section_size = block->i_buffer - data_section_offset;
/* AU header parsing */
bs_t bs;
bs_init(&bs, &block->p_buffer[2], block->i_buffer - 2);
while(auHeadersLength >= 8)
{
/* Get values from current AU header */
size_t pos = bs_pos(&bs);
uint32_t auSize = opaque->constantSize
? opaque->constantSize
: bs_read(&bs, opaque->sizeLength);
if(opaque->indexLength)
{
uint32_t auDeltaIndex = 0;
if(auIndex == (uint32_t) -1)
auIndex = bs_read(&bs, opaque->indexLength);
else
auDeltaIndex = bs_read(&bs, opaque->indexDeltaLength);
VLC_UNUSED(auDeltaIndex); // for when we'll reorder
}
uint32_t CTSDelta = 0, DTSDelta = 0, RAPFlag = 0;
if(opaque->CTSDeltaLength && bs_read(&bs, 1))
CTSDelta = bs_read(&bs, opaque->CTSDeltaLength);
if(opaque->DTSDeltaLength && bs_read(&bs, 1))
DTSDelta = bs_read(&bs, opaque->DTSDeltaLength);
if(opaque->randomAccessIndication)
RAPFlag = bs_read(&bs, 1);
if(data_section_size < auSize)
goto drop;
/* Extract AU payload data using the current AU header */
block_t *au = block_Alloc(auSize);
if(au)
{
memcpy(au->p_buffer, data_section, auSize);
if(CTSDelta)
block->i_pts += vlc_tick_from_samples(CTSDelta, pt->frequency);
if(DTSDelta)
block->i_dts = block->i_pts - vlc_tick_from_samples(DTSDelta, pt->frequency);
if(RAPFlag)
block->i_flags |= BLOCK_FLAG_TYPE_I;
h26x_output(sys, au, block->i_pts, true, info->m);
}
data_section += auSize;
data_section_size -= auSize;
if(bs_eof(&bs) || bs_error(&bs))
break;
auHeadersLength -= bs_pos(&bs) - pos;
}
}
else
{
h26x_output(sys, block, block->i_pts, true, info->m);
return;
}
drop:
block_Release(block);
}
static void rtp_mpeg4_release(struct vlc_rtp_pt *pt)
{
struct mpeg4_pt_opaque *opaque = pt->opaque;
if(opaque->config)
block_Release(opaque->config);
free(opaque);
}
static const struct vlc_rtp_pt_operations rtp_mpeg4v_ops = {
rtp_mpeg4_release, rtp_mpeg4v_init, rtp_mpeg4_destroy, rtp_mpeg4_decode,
};
static const struct vlc_rtp_pt_operations rtp_mpeg4a_ops = {
rtp_mpeg4_release, rtp_mpeg4a_init, rtp_mpeg4_destroy, rtp_mpeg4_decode,
};
static char hex_to_dec(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
return 0;
}
static block_t * mpeg4_decode_config(const char *psz, size_t len)
{
len &= ~1U;
if(len == 0)
return NULL;
block_t *config = block_Alloc(len/2);
if(config)
{
config->i_buffer = 0;
for(size_t i=0; i<len; i+=2)
{
uint8_t v = hex_to_dec(psz[i]) << 4;
v |= hex_to_dec(psz[i+1]);
config->p_buffer[config->i_buffer++] = v;
}
}
return config;
}
static int rtp_mpeg4_open(vlc_object_t *obj, struct vlc_rtp_pt *pt,
const struct vlc_sdp_pt *desc)
{
VLC_UNUSED(obj);
size_t sprop_len;
const char *sprop;
struct mpeg4_pt_opaque tmpOpaque = {0};
/* IETF RFC 6416 */
if (vlc_ascii_strcasecmp(desc->name, "MP4V-ES") == 0)
pt->ops = &rtp_mpeg4v_ops;
else if (vlc_ascii_strcasecmp(desc->name, "MP4A-LATM") == 0)
pt->ops = &rtp_mpeg4a_ops;
/* IETF RFC 3640 */
else if (vlc_ascii_strcasecmp(desc->name, "MPEG4-GENERIC") == 0)
{
sprop = vlc_sdp_fmtp_get_str(desc, "streamType", &sprop_len);
if (sprop && sprop_len)
{
tmpOpaque.streamType = strtod(sprop, NULL);
if(tmpOpaque.streamType == MPEG4_ST_VISUAL_STREAM)
pt->ops = &rtp_mpeg4v_ops;
else if(tmpOpaque.streamType == MPEG4_ST_AUDIO_STREAM)
pt->ops = &rtp_mpeg4a_ops;
else
return VLC_ENOTSUP;
}
/* FFmpeg does not set the streamType accordingly */
else pt->ops = &rtp_mpeg4a_ops;
/* Get variables for our AU header */
vlc_sdp_fmtp_get(desc, "sizeLength", &tmpOpaque.sizeLength);
if (tmpOpaque.sizeLength)
{
vlc_sdp_fmtp_get(desc, "indexLength", &tmpOpaque.indexLength);
vlc_sdp_fmtp_get(desc, "indexDeltaLength", &tmpOpaque.indexDeltaLength);
}
else
{
vlc_sdp_fmtp_get(desc, "constantSize", &tmpOpaque.constantSize);
}
vlc_sdp_fmtp_get(desc, "CTSDeltaLength", &tmpOpaque.CTSDeltaLength);
vlc_sdp_fmtp_get(desc, "DTSDeltaLength", &tmpOpaque.DTSDeltaLength);
vlc_sdp_fmtp_get(desc, "randomAccessIndication", &tmpOpaque.randomAccessIndication);
vlc_sdp_fmtp_get(desc, "auxiliaryDataSizeLength", &tmpOpaque.auxiliaryDataSizeLength);
// if(!tmpOpaque.constantSize && !tmpOpaque.sizeLength)
// return VLC_EINVAL;
}
else
return VLC_ENOTSUP;
struct mpeg4_pt_opaque *opaque = calloc(1, sizeof(*opaque));
if(!opaque)
return VLC_ENOMEM;
pt->opaque = opaque;
*opaque = tmpOpaque;
opaque->obj = obj;
// vlc_sdp_fmtp_get(desc, "profile-level-id", &opaque->profile_level_id);
vlc_sdp_fmtp_get(desc, "object", &opaque->object_id);
/*
* Video:
* - RFC 3640: Not defined (generic)
* - RFC 6416: MPEG-4 Visual configuration
* Audio:
* - RFC 3640: AudioSpecificConfig() element
* - RFC 6416: StreamMuxConfig() element
*/
sprop = vlc_sdp_fmtp_get_str(desc, "config", &sprop_len);
if (sprop && sprop_len)
opaque->config = mpeg4_decode_config(sprop, sprop_len);
return VLC_SUCCESS;
}
vlc_module_begin()
set_shortname(N_("RTP MPEG-4"))
set_description(N_("RTP MPEG-4 Visual and Audio payload parser"))
set_subcategory(SUBCAT_INPUT_DEMUX)
set_rtp_parser_callback(rtp_mpeg4_open)
add_shortcut("video/MP4V-ES", "audio/MP4A-LATM", "video/MPEG4-GENERIC", "audio/MPEG4-GENERIC")
vlc_module_end()