Files
UltraGrid/src/audio/codec.cpp
2025-04-14 15:44:10 +02:00

476 lines
18 KiB
C++

/**
* @file audio/codec.cpp
* @author Martin Pulec <pulec@cesnet.cz>
*/
/*
* Copyright (c) 2013-2023 CESNET, z. s. p. o.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, is permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of CESNET nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "audio/codec.h"
#include <algorithm>
#include <cassert>
#include <climits>
#include <cstdio> // for printf
#include <cstdlib> // for NULL, calloc, free, realloc
#include <cstring> // for strchr, memset, strtok_r
#include <string>
#include <unordered_map>
#include "audio/utils.h"
#include "compat/strings.h" // strdup, strcasecmp
#include "debug.h"
#include "lib_common.h"
#include "utils/color_out.h" // for col, SBOLD, SRED, TRED
#include "utils/macros.h"
#include "utils/misc.h"
#include "utils/string_view_utils.hpp"
#define MOD_NAME "[acodec] "
using std::get;
using std::hash;
using std::max;
using std::stoi;
using std::string;
using std::tuple;
using std::unordered_map;
using std::vector;
static const unordered_map<audio_codec_t, audio_codec_info_t, hash<int>>
audio_codec_info = {
{AC_NONE, { "(none)", 0 } },
{ AC_PCM, { "PCM", 0x0001 } },
{ AC_ALAW, { "A-law", 0x0006 } },
{ AC_MULAW, { "u-law", 0x0007 } },
{ AC_SPEEX, { "speex", 0xA109 } },
{ AC_OPUS, // fcc is "Opus", the TwoCC isn't defined
{ "Opus", 0x7375704F }},
{ AC_G722, { "G.722", 0x028F } },
{ AC_MP3, { "MP3", 0x0055 } },
{ AC_AAC, { "AAC", 0x00FF } },
{ AC_FLAC, { "FLAC", 0xF1AC } },
};
static struct audio_codec_state *audio_codec_init_real(const char *audio_codec_cfg,
audio_codec_direction_t direction, bool try_init);
struct audio_codec_state {
void **state;
int state_count;
const struct audio_compress_info *funcs;
audio_desc desc;
audio_codec_direction_t direction;
int bitrate;
};
static string
get_codec_desc(struct audio_codec_state *st)
{
if (st->funcs->get_codec_info == nullptr) {
return {};
}
char buf[STR_LEN];
const char *ret =
st->funcs->get_codec_info(st->state[0], sizeof buf, buf);
if (ret == nullptr) {
return {};
}
return ret;
}
vector<tuple<audio_codec_info_t, bool, string>>
get_audio_codec_list()
{
vector<tuple<audio_codec_info_t, bool, string>> ret;
for (auto const &it : audio_codec_info) {
if(it.first != AC_NONE) {
struct audio_codec_state *st = (struct audio_codec_state *)
audio_codec_init_real(get_name_to_audio_codec(it.first),
AUDIO_CODER, true);
bool available = false;
string desc;
if(st){
available = true;
desc = get_codec_desc(st);
audio_codec_done(st);
}
ret.emplace_back(it.second, available, desc);
}
}
return ret;
}
void list_audio_codecs(void) {
printf("Syntax:\n");
col() << SBOLD(
SRED("\t-A <codec_name>")
<< "[:sample_rate=<sampling_rate>][:bitrate=<bitrate>]\n");
col() << "\nwhere\n";
col() << "\t" << SBOLD("codec_name ") << " - one of the list below\n";
col() << "\t" << SBOLD("sample_rate")
<< " - sample rate that will the codec used (may differ from\n"
<< "\t\t captured)\n";
col() << "\t" << SBOLD("bitrate ") << " - codec bitrate "
<< SBOLD("per channel") << " (with optional k/M suffix)\n";
col() << "\n";
printf("Supported audio codecs:\n");
for (auto const &it : get_audio_codec_list()) {
string notes;
if (!get<1>(it)) {
notes += " " TRED("unavailable");
} else {
notes = get<2>(it);
}
col() << SBOLD("\t" << get<0>(it).name)
<< (notes.empty() ? "" : " - ") << notes << "\n";
}
col() << "\nCodecs marked as \"deprecated\" may be removed in future.\n"
"Codec coder marked with '*' is default.\n";
}
struct audio_codec_state *audio_codec_init(audio_codec_t audio_codec,
audio_codec_direction_t direction) {
return audio_codec_init_real(get_name_to_audio_codec(audio_codec), direction, false);
}
struct audio_codec_state *audio_codec_init_cfg(const char *audio_codec_cfg,
audio_codec_direction_t direction) {
return audio_codec_init_real(audio_codec_cfg, direction, false);
}
static struct audio_codec_state *audio_codec_init_real(const char *audio_codec_cfg,
audio_codec_direction_t direction, bool silent) {
const struct audio_codec_params params =
parse_audio_codec_params(audio_codec_cfg);
if (params.codec == AC_NONE) {
return nullptr;
}
void *state = NULL;
const struct audio_compress_info *aci = nullptr;
auto audio_compressions = get_libraries_for_class(LIBRARY_CLASS_AUDIO_COMPRESS, AUDIO_COMPRESS_ABI_VERSION);
for (auto const &it : audio_compressions) {
aci = static_cast<const struct audio_compress_info *>(it.second);
for (unsigned int j = 0; aci->supported_codecs[j] != AC_NONE; ++j) {
if (aci->supported_codecs[j] == params.codec) {
state = aci->init(params.codec, direction,
silent, params.bitrate);
if (state) {
break;
}
if (!silent) {
log_msg(LOG_LEVEL_ERROR,
"Error: initialization of "
"audio codec failed!\n");
}
}
}
if(state)
break;
}
if(!state) {
if (!silent) {
log_msg(LOG_LEVEL_ERROR,
"Unable to find encoder for audio codec '%s'\n",
get_name_to_audio_codec(params.codec));
}
return NULL;
}
struct audio_codec_state *s = (struct audio_codec_state *) calloc(1, sizeof(struct audio_codec_state));
s->state = (void **) calloc(1, sizeof(void*));
s->state[0] = state;
s->state_count = 1;
s->funcs = aci;
s->desc.codec = params.codec;
s->direction = direction;
s->bitrate = params.bitrate;
return s;
}
struct audio_codec_state *audio_codec_reconfigure(struct audio_codec_state *old,
audio_codec_t audio_codec, audio_codec_direction_t direction)
{
if (old != nullptr && old->desc.codec == audio_codec) {
return old;
}
audio_codec_done(old);
return audio_codec_init(audio_codec, direction);
}
/**
* Audio_codec_compress compresses given audio frame.
*
* This function has to be called iterativelly, in first iteration with frame, the others with NULL
*
* @param s state
* @param frame in first iteration audio frame to be compressed, in following NULL
* @retval pointer pointing to data
* @retval NULL indicating that there are no data left
*/
audio_frame2 audio_codec_compress(struct audio_codec_state *s, const audio_frame2 *frame)
{
audio_frame2 res;
if (frame != nullptr) {
if (s->state_count < frame->get_channel_count()) {
s->state = (void **) realloc(s->state, sizeof(void *) * frame->get_channel_count());
for (int i = s->state_count; i < frame->get_channel_count(); ++i) {
s->state[i] = s->funcs->init(s->desc.codec, s->direction, false, s->bitrate);
if (s->state[i] == nullptr) {
LOG(LOG_LEVEL_ERROR) << MOD_NAME << "Error: initialization of audio codec failed!\n";
return {};
}
}
s->state_count = frame->get_channel_count();
}
s->desc.ch_count = frame->get_channel_count();
s->desc.bps = frame->get_bps();
s->desc.sample_rate = frame->get_sample_rate();
res.set_timestamp(frame->get_timestamp());
}
audio_channel channel;
channel.timestamp = frame == nullptr ? -1 : frame->get_timestamp();
int nonzero_channels = 0;
for (int i = 0; i < s->desc.ch_count; ++i) {
audio_channel *encode_channel = NULL;
if(frame) {
audio_channel_demux(frame, i, &channel);
encode_channel = &channel;
}
audio_channel *out = s->funcs->compress(s->state[i], encode_channel);
if (out == nullptr) {
continue;
}
if (!res) {
res.init(s->desc.ch_count, out->codec, out->bps, out->sample_rate);
res.set_duration(out->duration);
} else {
assert(out->bps == res.get_bps()
&& out->sample_rate == res.get_sample_rate());
}
res.append(i, out->data, out->data_len);
res.set_timestamp(out->timestamp);
nonzero_channels += 1;
}
if (nonzero_channels == 0) {
return {};
}
return res;
}
audio_frame2 audio_codec_decompress(struct audio_codec_state *s, audio_frame2 *frame)
{
if (s->state_count < frame->get_channel_count()) {
s->state = (void **) realloc(s->state, sizeof(void *) * frame->get_channel_count());
for(int i = s->state_count; i < frame->get_channel_count(); ++i) {
s->state[i] = s->funcs->init(s->desc.codec, s->direction, false, 0);
if(s->state[i] == NULL) {
log_msg(LOG_LEVEL_ERROR,
"Error: initialization of audio codec "
"failed!\n");
return {};
}
}
s->state_count = frame->get_channel_count();
}
#if 0
if (s->out->ch_count != frame->ch_count) {
s->out->ch_count = frame->ch_count;
}
#endif
audio_frame2 ret;
audio_channel channel;
int nonzero_channels = 0;
bool out_frame_initialized = false;
for (int i = 0; i < frame->get_channel_count(); ++i) {
audio_channel_demux(frame, i, &channel);
if (channel.data_len == 0) {
continue;
}
audio_channel *out = s->funcs->decompress(s->state[i], &channel);
if (out) {
if (!out_frame_initialized) {
ret.init(frame->get_channel_count(), AC_PCM, out->bps, out->sample_rate);
ret.set_timestamp(frame->get_timestamp());
out_frame_initialized = true;
} else {
assert(out->bps == ret.get_bps()
&& out->sample_rate == ret.get_sample_rate());
}
ret.append(i, out->data, out->data_len);
nonzero_channels += 1;
}
}
if (nonzero_channels == 0 &&
frame->get_data_len() == 0) { // produced by acap/passive
ret.init(frame->get_channel_count(), AC_PCM, frame->get_bps(),
frame->get_sample_rate());
return ret;
}
if (nonzero_channels != frame->get_channel_count()) {
log_msg(LOG_LEVEL_WARNING,
"[Audio decompress] %d empty channel(s) returned!\n",
frame->get_channel_count() - nonzero_channels);
return {};
}
int max_len = 0;
for(int i = 0; i < frame->get_channel_count(); ++i) {
max_len = max<int>(max_len, ret.get_data_len(i));
}
for(int i = 0; i < frame->get_channel_count(); ++i) {
int len = ret.get_data_len(i);
if (len != max_len) {
LOG(LOG_LEVEL_WARNING) << "[Audio decompress] Inequal channel length detected (" << ret.get_data_len(i) << " vs " << max_len << ")!\n",
ret.resize(i, max_len);
memset(ret.get_data(i) + len, 0, max_len - len);
}
}
return ret;
}
const int *audio_codec_get_supported_samplerates(struct audio_codec_state *s)
{
return s->funcs->get_samplerates(s->state[0]);
}
void audio_codec_done(struct audio_codec_state *s)
{
if(!s)
return;
for(int i = 0; i < s->state_count; ++i) {
s->funcs->done(s->state[i]);
}
free(s->state);
free(s);
}
static audio_codec_t
get_audio_codec(const char *codec)
{
for (auto const &it : audio_codec_info) {
if (strcasecmp(it.second.name, codec) == 0) {
return it.first;
}
}
if (strcasecmp(codec, "PCMA") == 0) {
return AC_ALAW;
}
if (strcasecmp(codec, "PCMU") == 0) {
return AC_MULAW;
}
return AC_NONE;
}
struct audio_codec_params
parse_audio_codec_params(const char *ccfg)
{
struct audio_codec_params params {
};
std::string_view sv = ccfg;
while (!sv.empty()) {
std::string item = std::string(tokenize(sv, ':'));
if (params.codec == AC_NONE) {
params.codec = get_audio_codec(item.c_str());
if (params.codec == AC_NONE) {
MSG(ERROR,
"Unknown audio codec \"%s\" given!\n",
item.c_str());
return {};
}
continue;
}
if (IS_KEY_PREFIX(item.c_str(), "sample_rate")) {
params.sample_rate =
stoi(item.substr(item.find('=') + 1));
if (params.sample_rate < 0) {
MSG(ERROR,
"Sample rate cannot be negative! given "
"%d\n",
params.sample_rate);
return {};
}
} else if (IS_KEY_PREFIX(item.c_str(), "bitrate")) {
std::string val = item.substr(item.find('=') + 1);
const char *endptr = nullptr;
long long rate = unit_evaluate(val.c_str(), &endptr);
if (rate <= 0 || rate > INT_MAX || endptr[0] != '\0') {
LOG(LOG_LEVEL_ERROR)
<< "Wrong bitrate: " << val << "\n";
return {};
}
params.bitrate = (int) rate;
} else {
MSG(ERROR, "Unknown audio option: %s\n", item.c_str());
return {};
}
}
return params;
}
const char *get_name_to_audio_codec(audio_codec_t codec)
{
return audio_codec_info.at(codec).name;
}
uint32_t get_audio_tag(audio_codec_t codec)
{
return audio_codec_info.at(codec).tag;
}
audio_codec_t get_audio_codec_to_tag(uint32_t tag)
{
for (auto const &it : audio_codec_info) {
if(it.second.tag == tag) {
return it.first;
}
}
return AC_NONE;
}