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.
 
 
 
 
 
 

504 lines
16 KiB

/*****************************************************************************
* SDIAudioMultiplex.cpp: SDI Audio Multiplexing
*****************************************************************************
* Copyright © 2018 VideoLabs, VideoLAN and VideoLAN Authors
*
* This program 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 program 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 program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "SDIAudioMultiplex.hpp"
#include <vlc_es.h>
#include <limits>
#include <cstring>
#include <algorithm>
using namespace sdi_sout;
SDIAudioMultiplexBuffer::SDIAudioMultiplexBuffer(vlc_object_t *obj)
: AES3AudioBuffer(obj, 2), AbstractStreamOutputBuffer()
{
}
SDIAudioMultiplexBuffer::~SDIAudioMultiplexBuffer()
{
FlushQueued();
}
void SDIAudioMultiplexBuffer::FlushQueued()
{
}
void SDIAudioMultiplexBuffer::Enqueue(void *p)
{
AES3AudioBuffer::push(reinterpret_cast<block_t *>(p));
}
void * SDIAudioMultiplexBuffer::Dequeue()
{
return NULL;
}
static void ConfigureChannels(unsigned i, es_format_t *fmt)
{
if( i>=8 )
{
i = 8;
fmt->audio.i_physical_channels = AOUT_CHANS_7_1;
}
else if( i>2 )
{
i = 6;
fmt->audio.i_physical_channels = AOUT_CHANS_5_1;
}
else
{
fmt->audio.i_physical_channels = AOUT_CHANS_STEREO;
}
fmt->audio.i_channels = i;
fmt->audio.i_blockalign = i * 16 / 8;
}
SDIAudioMultiplexConfig::Mapping::Mapping(vlc_object_t *obj, const StreamID &id)
: id(id), buffer(obj)
{
es_format_Init(&fmt, AUDIO_ES, VLC_CODEC_S16N);
fmt.audio.i_format = VLC_CODEC_S16N;
fmt.audio.i_rate = 48000;
fmt.audio.i_bitspersample = 16;
ConfigureChannels(2, &fmt);
b_decode = true;
}
SDIAudioMultiplexConfig::Mapping::~Mapping()
{
es_format_Clean(&fmt);
}
SDIAudioMultiplexConfig::SDIAudioMultiplexConfig(vlc_object_t *obj, uint8_t channels)
{
this->obj = obj;
subframeslotbitmap = 0;
if(channels > 8)
framewidth = 8;
else if(channels > 2)
framewidth = 4;
else
framewidth = 1;
b_accept_any = true;
}
SDIAudioMultiplexConfig::~SDIAudioMultiplexConfig()
{
for(size_t i=0; i<mappings.size(); i++)
delete mappings[i];
}
bool SDIAudioMultiplexConfig::decode(const StreamID &id) const
{
const Mapping *map = getMappingByID(id);
if(map)
return map->b_decode;
return true;
}
bool SDIAudioMultiplexConfig::SubFrameSlotUsed(uint8_t i) const
{
return (1 << i) & subframeslotbitmap;
}
void SDIAudioMultiplexConfig::setSubFrameSlotUsed(uint8_t i)
{
subframeslotbitmap |= (1 << i);
}
void SDIAudioMultiplexConfig::parseConfiguration(vlc_object_t *obj, const char *psz)
{
char *name = NULL;
char *psz_in = (char*)psz;
config_chain_t *p_config_chain = NULL;
while(psz_in)
{
char *psz_next = config_ChainCreate(&name, &p_config_chain, psz_in);
if(name)
{
if(!std::strcmp(name, "only"))
{
b_accept_any = false;
msg_Dbg(obj, "only accepting declared streams");
}
else /* try mapping decl */
{
int i_id = -1;
int i_seqid = -1;
int *pi_id = &i_seqid;
const char *psz_id = name;
if(psz_id[0]=='#')
{
psz_id++;
pi_id = &i_id;
}
if(*psz_id)
{
char *end = NULL;
int i_val = std::strtol(psz_id, &end, 10);
if(end != NULL && *end == '\0')
*pi_id = i_val;
}
if(i_id != -1 || i_seqid != -1)
{
msg_Dbg(obj,"found declaration for ES %s %d",
(i_id > -1) ? "pid #" : "seq", *pi_id);
bool b_embed = false;
int i_reserved_chans = 0;
std::vector<uint8_t> subframeslots;
for(config_chain_t *p = p_config_chain; p; p = p->p_next)
{
if(!std::strcmp("embed", p->psz_name))
{
b_embed = true;
msg_Dbg(obj," * mode passthrough set");
}
else if(!std::strcmp("chans", p->psz_name) && subframeslots.empty())
{
char *end = NULL;
int i_val = std::strtol(p->psz_value, &end, 10);
if(end != NULL && *end == '\0')
{
i_reserved_chans = i_val;
msg_Dbg(obj," * provisioned %d channels", i_val);
}
else msg_Warn(obj, " * ignoring channels count declaration %d", i_val);
}
else if(i_reserved_chans == 0)
{
char *end = NULL;
int i_slot = std::strtol(p->psz_name, &end, 10);
if(end != NULL && *end == '\0')
{
if(i_slot < MAX_AES3_AUDIO_SUBFRAMES && i_slot < (2 * framewidth) &&
std::find(subframeslots.begin(), subframeslots.end(), i_slot) == subframeslots.end())
{
subframeslots.push_back(i_slot);
msg_Dbg(obj," * mapped channel %zd to subframe %d",
subframeslots.size(), i_slot);
}
else msg_Warn(obj, " * ignoring invalid subframe declaration %d", i_slot);
}
else msg_Warn(obj, " * ignoring unknown/invalid token %s", p->psz_name);
}
}
bool b_success = false;
if(b_embed)
b_success = addMappingEmbed(StreamID(i_id, i_seqid));
else if(subframeslots.empty() && i_reserved_chans)
b_success = addMapping(StreamID(i_id, i_seqid), i_reserved_chans);
else if(!subframeslots.empty())
b_success = addMapping(StreamID(i_id, i_seqid), subframeslots);
if(b_success)
msg_Dbg(obj, " * successfully configured");
else
msg_Warn(obj, " * configuration rejected (duplicate or not enough subframes ?)");
}
}
free(name);
}
config_ChainDestroy(p_config_chain);
if(psz != psz_in)
free(psz_in);
psz_in = psz_next;
}
}
std::vector<uint8_t> SDIAudioMultiplexConfig::getFreeSubFrameSlots(bool b_aligned) const
{
std::vector<uint8_t> slots;
for(uint8_t i=0; i<getMultiplexedFramesCount() * 2; i++)
{
if(!SubFrameSlotUsed(i))
slots.push_back(i);
}
for( ; b_aligned && slots.size() >= 2; slots.erase(slots.begin()))
{
/* get aligned subframes pair */
if((slots[0] & 1) == 0 && slots[1] == slots[0] + 1)
break;
}
return slots;
}
std::vector<uint8_t> SDIAudioMultiplexConfig::getConfiguredSlots(const StreamID &id) const
{
for(size_t i=0; i<mappings.size(); i++)
{
if(mappings[i]->id == id)
return mappings[i]->subframesslots;
}
return std::vector<uint8_t>();
}
bool SDIAudioMultiplexConfig::addMapping(const StreamID &id, const es_format_t *fmt)
{
if(!fmt->audio.i_channels || !b_accept_any)
return false;
return addMapping(id, fmt->audio.i_channels);
}
bool SDIAudioMultiplexConfig::addMapping(const StreamID &id, unsigned channels)
{
std::vector<uint8_t> slots = getFreeSubFrameSlots();
if(slots.size() < channels)
return false;
slots.resize(channels);
return addMapping(id, slots);
}
bool SDIAudioMultiplexConfig::addMappingEmbed(const StreamID &id, std::vector<uint8_t> slots)
{
if(slots.empty())
slots = getFreeSubFrameSlots(true);
if(slots.size() < 2)
return false;
slots.resize(2);
bool b = addMapping(id, slots);
if(b)
getMappingByID(id)->b_decode = false;
return b;
}
bool SDIAudioMultiplexConfig::addMapping(const StreamID &id, std::vector<uint8_t> subframeslots)
{
for(size_t i=0; i<mappings.size(); i++)
if(mappings[i]->id == id)
return false;
for(size_t i=0; i<subframeslots.size(); i++)
if(SubFrameSlotUsed(subframeslots[i]))
return false;
Mapping *assoc = new Mapping(obj, id);
assoc->subframesslots = subframeslots;
mappings.push_back(assoc);
for(size_t i=0; i<subframeslots.size(); i++)
setSubFrameSlotUsed(subframeslots[i]);
return true;
}
SDIAudioMultiplexConfig::Mapping *
SDIAudioMultiplexConfig::getMappingByID(const StreamID &id)
{
auto it = std::find_if(mappings.begin(), mappings.end(),
[&id](Mapping *e) { return e->id == id; });
return (it != mappings.end()) ? *it : NULL;
}
const SDIAudioMultiplexConfig::Mapping *
SDIAudioMultiplexConfig::getMappingByID(const StreamID &id) const
{
auto it = std::find_if(mappings.begin(), mappings.end(),
[&id](const Mapping *e) { return e->id == id; });
return (it != mappings.end()) ? *it : NULL;
}
unsigned SDIAudioMultiplexConfig::getMaxSamplesForBlockSize(size_t s) const
{
return s / (2 * sizeof(uint16_t) * getMultiplexedFramesCount());
}
SDIAudioMultiplexBuffer *
SDIAudioMultiplexConfig::getBufferForStream(const StreamID &id)
{
Mapping *map = getMappingByID(id);
return map ? &map->buffer : NULL;
}
const es_format_t * SDIAudioMultiplexConfig::getConfigurationForStream(const StreamID &id) const
{
const Mapping *map = getMappingByID(id);
return map ? &map->fmt : NULL;
}
const es_format_t *
SDIAudioMultiplexConfig::updateFromRealESConfig(const StreamID &id,
const es_format_t *fmt)
{
Mapping *mapping = getMappingByID(id);
if(mapping)
{
if(mapping->subframesslots.size() > 2 && fmt->audio.i_channels > 2)
ConfigureChannels(fmt->audio.i_channels, &mapping->fmt);
mapping->buffer.setSubFramesCount(mapping->fmt.audio.i_channels);
return &mapping->fmt;
}
assert(0);
return NULL;
}
SDIAudioMultiplex::SDIAudioMultiplex(vlc_object_t *obj, uint8_t channels)
: config(SDIAudioMultiplexConfig(obj, channels))
{
p_obj = obj;
head = VLC_TICK_INVALID;
}
SDIAudioMultiplex::~SDIAudioMultiplex()
{
}
unsigned SDIAudioMultiplex::availableVirtualSamples(vlc_tick_t from) const
{
unsigned samples = std::numeric_limits<unsigned>::max();
for(size_t i=0; i<MAX_AES3_AUDIO_FRAMES; i++)
{
if(framesources[i].subframe0.available() &&
framesources[i].subframe1.available())
continue;
samples = std::min(samples, framesources[i].availableVirtualSamples(from));
}
return samples < std::numeric_limits<unsigned>::max() ? samples : 0;
}
unsigned SDIAudioMultiplex::alignedInterleaveInSamples(vlc_tick_t from, unsigned i_wanted) const
{
unsigned i_align = i_wanted;
for(size_t i=0; i<MAX_AES3_AUDIO_FRAMES; i++)
{
if(!framesources[i].subframe0.available())
i_align = std::min(i_align, framesources[i].subframe0.alignedInterleaveInSamples(from, i_wanted));
if(!framesources[i].subframe1.available())
i_align = std::min(i_align, framesources[i].subframe1.alignedInterleaveInSamples(from, i_wanted));
}
return i_align;
}
vlc_tick_t SDIAudioMultiplex::bufferStart() const
{
vlc_tick_t start = VLC_TICK_INVALID;
for(size_t i=0; i<MAX_AES3_AUDIO_FRAMES; i++)
{
if(framesources[i].subframe0.available() &&
framesources[i].subframe1.available())
continue;
vlc_tick_t t = framesources[i].bufferStartTime();
if(start == VLC_TICK_INVALID ||
(t != VLC_TICK_INVALID && t<start))
start = t;
}
return start;
}
unsigned SDIAudioMultiplex::getFreeSubFrameSlots() const
{
unsigned bitfield = 0;
for(unsigned i=0; i<MAX_AES3_AUDIO_FRAMES; i++)
{
const AES3AudioFrameSource *source = &framesources[i];
if(source->subframe0.available())
bitfield |= (1 << (i * 2 + 0));
if(source->subframe1.available())
bitfield |= (1 << (i * 2 + 1));
}
return bitfield;
}
void SDIAudioMultiplex::SetSubFrameSource(uint8_t n, AES3AudioBuffer *buf,
AES3AudioSubFrameIndex idx)
{
assert(n<MAX_AES3_AUDIO_SUBFRAMES);
AES3AudioFrameSource *f = &framesources[n / 2];
AES3AudioSubFrameSource *s = (n & 1) ? &f->subframe1 : &f->subframe0;
assert(s->available());
*s = AES3AudioSubFrameSource(buf, idx);
}
#ifdef SDI_MULTIPLEX_DEBUG
void SDIAudioMultiplex::Debug() const
{
msg_Dbg(p_obj, "Multiplex: head %ld bufferstart() %ld", head, bufferStart());
for(unsigned i=0; i<MAX_AES3_AUDIO_FRAMES; i++)
{
const AES3AudioFrameSource *source = &framesources[i];
if(!source->subframe0.available())
msg_Dbg(p_obj, " [%d.0] bufferstart() %ld", i, source->subframe0.bufferStartTime());
if(!source->subframe1.available())
msg_Dbg(p_obj, " [%d.1] bufferstart() %ld", i, source->subframe1.bufferStartTime());
}
}
#endif
block_t * SDIAudioMultiplex::Extract(unsigned samples)
{
vlc_tick_t start = bufferStart();
uint8_t interleavedframes = config.getMultiplexedFramesCount();
/* Ensure we never roll back due to late fifo */
if(head != VLC_TICK_INVALID)
{
if(start < head)
{
for(unsigned i=0; i<MAX_AES3_AUDIO_FRAMES; i++)
framesources[i].forwardTo(head);
}
start = head;
}
block_t *p_block = block_Alloc( interleavedframes * 2 * sizeof(uint16_t) * samples );
if(!p_block)
return NULL;
memset(p_block->p_buffer, 0, p_block->i_buffer);
p_block->i_pts = p_block->i_dts = start;
p_block->i_nb_samples = samples;
for(unsigned i=0; i<MAX_AES3_AUDIO_FRAMES; i++)
{
AES3AudioFrameSource *source = &framesources[i];
unsigned ahead = source->availableVirtualSamples(start);
if(ahead == 0)
continue;
#ifdef SDI_MULTIPLEX_DEBUG
vlc_fourcc_t i_codec = source->subframe0.getCodec();
msg_Dbg(p_obj, "%4.4s pair %u tocopy %u from %ld head %ld, avail %u",
reinterpret_cast<const char *>(&i_codec), i, samples,
start, source->bufferStartTime(), ahead);
#endif
source->subframe0.copy(p_block->p_buffer, samples, start, (i * 2 + 0), interleavedframes);
source->subframe1.copy(p_block->p_buffer, samples, start, (i * 2 + 1), interleavedframes);
}
for(unsigned i=0; i<MAX_AES3_AUDIO_FRAMES; i++)
framesources[i].tagVirtualConsumed(start, samples);
for(unsigned i=0; i<MAX_AES3_AUDIO_FRAMES; i++)
framesources[i].flushConsumed();
head = bufferStart();
return p_block;
}