mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-04-29 06:46:38 -04:00
Add AAC-ELD vendor codec. This is based on the documentation by Sa Xiao: Link: https://github.com/wasdwasd0105/PicoW-usb2bt-audio/blob/main/aac-eld-apple.md
671 lines
16 KiB
C
671 lines
16 KiB
C
/* Spa A2DP AAC-ELD-A(irpods) codec */
|
|
/* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */
|
|
/* SPDX-FileCopyrightText: Copyright © 2026 Pauli Virtanen */
|
|
/* SPDX-License-Identifier: MIT */
|
|
|
|
#include <unistd.h>
|
|
#include <stddef.h>
|
|
#include <errno.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include <spa/param/audio/format.h>
|
|
#include <spa/utils/dict.h>
|
|
|
|
#include <fdk-aac/aacenc_lib.h>
|
|
#include <fdk-aac/aacdecoder_lib.h>
|
|
|
|
#include "rtp.h"
|
|
#include "media-codecs.h"
|
|
#include "aac-bits.h"
|
|
|
|
static struct spa_log *global_log;
|
|
|
|
#define DEFAULT_AAC_BITRATE 256000
|
|
|
|
#define FRAMES_PER_PACKET 3
|
|
#define FRAME_SAMPLES 480
|
|
#define SAMPLE_SIZE 2 /* S16 */
|
|
|
|
struct frame_header {
|
|
uint8_t data[4];
|
|
} __attribute__((packed));
|
|
|
|
struct impl {
|
|
HANDLE_AACENCODER enc;
|
|
HANDLE_AACDECODER dec;
|
|
|
|
int codesize;
|
|
|
|
uint32_t enc_delay;
|
|
uint32_t dec_delay;
|
|
|
|
uint16_t enc_seq;
|
|
};
|
|
|
|
static bool parse_frame_header(const struct frame_header *hdr, uint16_t *seq, uint16_t *size)
|
|
{
|
|
const uint8_t *buf = hdr->data;
|
|
|
|
if ((buf[0] & 0xf0) != 0xb0)
|
|
return false;
|
|
*seq = (buf[0] & 0x0f) << 8 | buf[1];
|
|
|
|
if ((buf[2] & 0xf0) != 0x10)
|
|
return false;
|
|
*size = (buf[2] & 0x0f) << 8 | buf[3];
|
|
|
|
return true;
|
|
}
|
|
|
|
static void write_frame_header(struct frame_header *hdr, uint16_t seq, uint16_t size)
|
|
{
|
|
uint8_t *buf = hdr->data;
|
|
|
|
buf[0] = 0xb0 | (seq & 0xf00) >> 8;
|
|
buf[1] = seq & 0xff;
|
|
buf[2] = 0x10 | (size & 0xf00) >> 8;
|
|
buf[3] = size & 0xff;
|
|
}
|
|
|
|
static bool eld_supported(void)
|
|
{
|
|
static bool supported = false, checked = false;
|
|
HANDLE_AACENCODER enc = NULL;
|
|
|
|
if (checked)
|
|
return supported;
|
|
|
|
if (aacEncOpen(&enc, 0, 2) != AACENC_OK)
|
|
goto done;
|
|
if (aacEncoder_SetParam(enc, AACENC_AOT, AOT_ER_AAC_ELD) != AACENC_OK)
|
|
goto done;
|
|
if (aacEncoder_SetParam(enc, AACENC_SBR_MODE, 1) != AACENC_OK)
|
|
goto done;
|
|
|
|
supported = true;
|
|
|
|
done:
|
|
if (enc)
|
|
aacEncClose(&enc);
|
|
checked = true;
|
|
spa_log_debug(global_log, "FDK-AAC AAC-ELD-A support:%d", (int)supported);
|
|
return supported;
|
|
}
|
|
|
|
static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
|
|
const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE])
|
|
{
|
|
const a2dp_aac_eld_a_t a2dp_aac = {
|
|
.info = codec->vendor,
|
|
AAC_ELD_A_INIT_AOT(AAC_ELD_A_AOT_AAC_ELD),
|
|
AAC_ELD_A_INIT_FREQ_CH(AAC_ELD_A_FREQ_48000, AAC_ELD_A_CH_MONO | AAC_ELD_A_CH_STEREO),
|
|
AAC_ELD_A_INIT_FLAGS_BITRATE(AAC_ELD_A_FLAG_VBR, DEFAULT_AAC_BITRATE),
|
|
};
|
|
|
|
if (!eld_supported())
|
|
return -ENOTSUP;
|
|
|
|
memcpy(caps, &a2dp_aac, sizeof(a2dp_aac));
|
|
return sizeof(a2dp_aac);
|
|
}
|
|
|
|
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
|
|
const void *caps, size_t caps_size,
|
|
const struct media_codec_audio_info *info,
|
|
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
|
|
void **config_data)
|
|
{
|
|
a2dp_aac_eld_a_t conf;
|
|
int aot, freq, ch, cflags, bitrate;
|
|
|
|
if (!eld_supported())
|
|
return -ENOTSUP;
|
|
|
|
if (caps_size < sizeof(conf))
|
|
return -EINVAL;
|
|
|
|
memcpy(&conf, caps, sizeof(conf));
|
|
|
|
if (codec->vendor.vendor_id != conf.info.vendor_id ||
|
|
codec->vendor.codec_id != conf.info.codec_id)
|
|
return -ENOTSUP;
|
|
|
|
if (AAC_ELD_A_GET_AOT(conf) & AAC_ELD_A_AOT_AAC_ELD)
|
|
aot = AAC_ELD_A_AOT_AAC_ELD;
|
|
else
|
|
return -EINVAL;
|
|
|
|
if (AAC_ELD_A_GET_FREQ(conf) & AAC_ELD_A_FREQ_48000)
|
|
freq = AAC_ELD_A_FREQ_48000;
|
|
else
|
|
return -EINVAL;
|
|
|
|
if (AAC_ELD_A_GET_CH(conf) & AAC_ELD_A_CH_STEREO)
|
|
ch = AAC_ELD_A_CH_STEREO;
|
|
else if (AAC_ELD_A_GET_CH(conf) & AAC_ELD_A_CH_MONO)
|
|
ch = AAC_ELD_A_CH_MONO;
|
|
else
|
|
return -EINVAL;
|
|
|
|
if (AAC_ELD_A_GET_FLAGS(conf) & AAC_ELD_A_FLAG_VBR)
|
|
cflags = AAC_ELD_A_FLAG_VBR;
|
|
else
|
|
cflags = 0;
|
|
|
|
bitrate = AAC_ELD_A_GET_BITRATE(conf);
|
|
|
|
conf = (a2dp_aac_eld_a_t) {
|
|
.info = conf.info,
|
|
AAC_ELD_A_INIT_AOT(aot),
|
|
AAC_ELD_A_INIT_FREQ_CH(freq, ch),
|
|
AAC_ELD_A_INIT_FLAGS_BITRATE(cflags, bitrate),
|
|
};
|
|
|
|
memcpy(config, &conf, sizeof(conf));
|
|
|
|
return sizeof(conf);
|
|
}
|
|
|
|
static int codec_validate_config(const struct media_codec *codec, uint32_t flags,
|
|
const void *caps, size_t caps_size,
|
|
struct spa_audio_info *info)
|
|
{
|
|
a2dp_aac_eld_a_t conf;
|
|
|
|
if (caps == NULL || caps_size < sizeof(conf))
|
|
return -EINVAL;
|
|
|
|
memcpy(&conf, caps, sizeof(conf));
|
|
|
|
spa_zero(*info);
|
|
info->media_type = SPA_MEDIA_TYPE_audio;
|
|
info->media_subtype = SPA_MEDIA_SUBTYPE_raw;
|
|
info->info.raw.format = SPA_AUDIO_FORMAT_S16;
|
|
|
|
if (AAC_ELD_A_GET_AOT(conf) != AAC_ELD_A_AOT_AAC_ELD)
|
|
return -EINVAL;
|
|
|
|
switch (AAC_ELD_A_GET_FREQ(conf)) {
|
|
case AAC_ELD_A_FREQ_48000:
|
|
info->info.raw.rate = 48000;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (AAC_ELD_A_GET_CH(conf)) {
|
|
case AAC_ELD_A_CH_STEREO:
|
|
info->info.raw.channels = 2;
|
|
info->info.raw.position[0] = SPA_AUDIO_CHANNEL_FL;
|
|
info->info.raw.position[1] = SPA_AUDIO_CHANNEL_FR;
|
|
break;
|
|
case AAC_ELD_A_CH_MONO:
|
|
info->info.raw.channels = 1;
|
|
info->info.raw.position[0] = SPA_AUDIO_CHANNEL_MONO;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
|
|
const void *caps, size_t caps_size, uint32_t id, uint32_t idx,
|
|
struct spa_pod_builder *b, struct spa_pod **param)
|
|
{
|
|
struct spa_audio_info info;
|
|
struct spa_pod_frame f[1];
|
|
int res;
|
|
|
|
if ((res = codec_validate_config(codec, flags, caps, caps_size, &info)) < 0)
|
|
return res;
|
|
|
|
if (idx > 0)
|
|
return 0;
|
|
|
|
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id);
|
|
spa_pod_builder_add(b,
|
|
SPA_FORMAT_mediaType, SPA_POD_Id(info.media_type),
|
|
SPA_FORMAT_mediaSubtype, SPA_POD_Id(info.media_subtype),
|
|
SPA_FORMAT_AUDIO_format, SPA_POD_Id(info.info.raw.format),
|
|
SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info.info.raw.rate),
|
|
SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info.info.raw.channels),
|
|
SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t),
|
|
SPA_TYPE_Id, info.info.raw.channels, info.info.raw.position),
|
|
0);
|
|
|
|
*param = spa_pod_builder_pop(b, &f[0]);
|
|
return *param == NULL ? -EIO : 1;
|
|
}
|
|
|
|
static void *codec_init(const struct media_codec *codec, uint32_t flags,
|
|
void *config, size_t config_len, const struct spa_audio_info *info,
|
|
void *props, size_t mtu)
|
|
{
|
|
struct impl *this = NULL;
|
|
a2dp_aac_eld_a_t conf;
|
|
struct spa_audio_info config_info;
|
|
uint8_t asc_buf[8];
|
|
UCHAR *asc[1] = { (void *)asc_buf };
|
|
UINT asc_len[1];
|
|
UINT bitratemode;
|
|
int bitrate, max_bitrate;
|
|
int res;
|
|
size_t rate, channels;
|
|
|
|
if (config_len < sizeof(conf)) {
|
|
res = -EINVAL;
|
|
goto error;
|
|
}
|
|
memcpy(&conf, config, sizeof(conf));
|
|
|
|
if (info->media_type != SPA_MEDIA_TYPE_audio ||
|
|
info->media_subtype != SPA_MEDIA_SUBTYPE_raw ||
|
|
info->info.raw.format != SPA_AUDIO_FORMAT_S16) {
|
|
res = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
if ((res = codec_validate_config(codec, flags, config, config_len, &config_info)) < 0)
|
|
goto error;
|
|
if (config_info.info.raw.channels != info->info.raw.channels ||
|
|
config_info.info.raw.rate != info->info.raw.rate) {
|
|
res = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
this = calloc(1, sizeof(struct impl));
|
|
if (this == NULL) {
|
|
res = -errno;
|
|
goto error;
|
|
}
|
|
|
|
mtu = mtu;
|
|
rate = config_info.info.raw.rate;
|
|
channels = config_info.info.raw.channels;
|
|
|
|
/* No fragmentation: make sure it fits into MTU */
|
|
if (mtu <= sizeof(struct rtp_header) + sizeof(struct frame_header) * FRAMES_PER_PACKET)
|
|
goto error;
|
|
|
|
max_bitrate = (mtu - sizeof(struct rtp_header) - sizeof(struct frame_header) * FRAMES_PER_PACKET)
|
|
* 8 * rate / FRAME_SAMPLES / FRAMES_PER_PACKET;
|
|
bitrate = SPA_MIN(max_bitrate, AAC_ELD_A_GET_BITRATE(conf));
|
|
|
|
if (AAC_ELD_A_GET_FLAGS(conf) & AAC_ELD_A_FLAG_VBR)
|
|
bitratemode = 5;
|
|
else
|
|
bitratemode = 0;
|
|
|
|
/*
|
|
* Encoder
|
|
*/
|
|
res = aacEncOpen(&this->enc, 0, channels);
|
|
if (res != AACENC_OK)
|
|
goto error;
|
|
|
|
res = aacEncoder_SetParam(this->enc, AACENC_AOT, AOT_ER_AAC_ELD);
|
|
if (res != AACENC_OK)
|
|
goto error;
|
|
|
|
res = aacEncoder_SetParam(this->enc, AACENC_SBR_RATIO, 1);
|
|
if (res != AACENC_OK)
|
|
goto error;
|
|
|
|
res = aacEncoder_SetParam(this->enc, AACENC_SBR_MODE, 1);
|
|
if (res != AACENC_OK)
|
|
goto error;
|
|
|
|
res = aacEncoder_SetParam(this->enc, AACENC_CHANNELMODE, channels);
|
|
if (res != AACENC_OK)
|
|
goto error;
|
|
|
|
res = aacEncoder_SetParam(this->enc, AACENC_SAMPLERATE, rate);
|
|
if (res != AACENC_OK)
|
|
goto error;
|
|
|
|
res = aacEncoder_SetParam(this->enc, AACENC_GRANULE_LENGTH, FRAME_SAMPLES);
|
|
if (res != AACENC_OK)
|
|
goto error;
|
|
|
|
res = aacEncoder_SetParam(this->enc, AACENC_PEAK_BITRATE, max_bitrate);
|
|
if (res != AACENC_OK)
|
|
goto error;
|
|
|
|
res = aacEncoder_SetParam(this->enc, AACENC_BITRATE, bitrate);
|
|
if (res != AACENC_OK)
|
|
goto error;
|
|
|
|
res = aacEncoder_SetParam(this->enc, AACENC_BITRATEMODE, bitratemode);
|
|
if (res != AACENC_OK)
|
|
goto error;
|
|
|
|
res = aacEncoder_SetParam(this->enc, AACENC_TRANSMUX, TT_MP4_RAW);
|
|
if (res != AACENC_OK)
|
|
goto error;
|
|
|
|
res = aacEncoder_SetParam(this->enc, AACENC_AFTERBURNER, 1);
|
|
if (res != AACENC_OK)
|
|
goto error;
|
|
|
|
res = aacEncEncode(this->enc, NULL, NULL, NULL, NULL);
|
|
if (res != AACENC_OK)
|
|
goto error;
|
|
|
|
AACENC_InfoStruct enc_info = {};
|
|
res = aacEncInfo(this->enc, &enc_info);
|
|
if (res != AACENC_OK)
|
|
goto error;
|
|
|
|
this->enc_delay = enc_info.nDelay;
|
|
|
|
if (enc_info.frameLength != FRAME_SAMPLES)
|
|
goto error;
|
|
|
|
this->codesize = channels * FRAME_SAMPLES * SAMPLE_SIZE * FRAMES_PER_PACKET;
|
|
|
|
/*
|
|
* Decoder
|
|
*/
|
|
this->dec = aacDecoder_Open(TT_MP4_RAW, 1);
|
|
if (!this->dec) {
|
|
res = -EINVAL;
|
|
goto error;
|
|
}
|
|
#ifdef AACDECODER_LIB_VL0
|
|
res = aacDecoder_SetParam(this->dec, AAC_PCM_MIN_OUTPUT_CHANNELS, channels);
|
|
if (res != AAC_DEC_OK)
|
|
goto error;
|
|
res = aacDecoder_SetParam(this->dec, AAC_PCM_MAX_OUTPUT_CHANNELS, channels);
|
|
if (res != AAC_DEC_OK)
|
|
goto error;
|
|
#else
|
|
res = aacDecoder_SetParam(this->dec, AAC_PCM_OUTPUT_CHANNELS, channels);
|
|
if (res != AAC_DEC_OK)
|
|
goto error;
|
|
#endif
|
|
res = aac_make_asc(asc_buf, sizeof(asc_buf), AAC_AOT_ER_AAC_ELD,
|
|
rate, rate, channels, true);
|
|
if (res < 0)
|
|
goto error;
|
|
asc_len[0] = res;
|
|
res = aacDecoder_ConfigRaw(this->dec, asc, asc_len);
|
|
if (res != AAC_DEC_OK)
|
|
goto error;
|
|
|
|
this->dec_delay = 0;
|
|
|
|
return this;
|
|
|
|
error:
|
|
if (this && this->enc)
|
|
aacEncClose(&this->enc);
|
|
if (this && this->dec)
|
|
aacDecoder_Close(this->dec);
|
|
free(this);
|
|
errno = -res;
|
|
return NULL;
|
|
}
|
|
|
|
static void codec_deinit(void *data)
|
|
{
|
|
struct impl *this = data;
|
|
if (this->enc)
|
|
aacEncClose(&this->enc);
|
|
if (this->dec)
|
|
aacDecoder_Close(this->dec);
|
|
free(this);
|
|
}
|
|
|
|
static int codec_get_block_size(void *data)
|
|
{
|
|
struct impl *this = data;
|
|
return this->codesize;
|
|
}
|
|
|
|
static int codec_start_encode (void *data,
|
|
void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp)
|
|
{
|
|
struct rtp_header *header;
|
|
|
|
if (dst_size < sizeof(struct rtp_header))
|
|
return -ENOSPC;
|
|
|
|
header = (struct rtp_header *)dst;
|
|
memset(header, 0, sizeof(struct rtp_header));
|
|
|
|
header->v = 2;
|
|
header->pt = 96;
|
|
header->sequence_number = htons(seqnum);
|
|
header->timestamp = htonl(timestamp);
|
|
header->ssrc = htonl(1);
|
|
|
|
return sizeof(struct rtp_header);
|
|
}
|
|
|
|
static int encode_frame(struct impl *this,
|
|
const void *src, size_t src_size,
|
|
void *dst, size_t dst_size,
|
|
size_t *dst_out)
|
|
{
|
|
struct frame_header *hdr = dst;
|
|
int res;
|
|
|
|
if (dst_size < sizeof(*hdr))
|
|
return -ENOSPC;
|
|
|
|
dst_size -= sizeof(*hdr);
|
|
dst = SPA_PTROFF(dst, sizeof(*hdr), void);
|
|
|
|
void *in_bufs[] = {(void *) src};
|
|
int in_buf_ids[] = {IN_AUDIO_DATA};
|
|
int in_buf_sizes[] = {src_size};
|
|
int in_buf_el_sizes[] = {SAMPLE_SIZE};
|
|
AACENC_BufDesc in_buf_desc = {
|
|
.numBufs = 1,
|
|
.bufs = in_bufs,
|
|
.bufferIdentifiers = in_buf_ids,
|
|
.bufSizes = in_buf_sizes,
|
|
.bufElSizes = in_buf_el_sizes,
|
|
};
|
|
AACENC_InArgs in_args = {
|
|
.numInSamples = src_size / SAMPLE_SIZE,
|
|
};
|
|
|
|
void *out_bufs[] = {dst};
|
|
int out_buf_ids[] = {OUT_BITSTREAM_DATA};
|
|
int out_buf_sizes[] = {dst_size};
|
|
int out_buf_el_sizes[] = {SAMPLE_SIZE};
|
|
AACENC_BufDesc out_buf_desc = {
|
|
.numBufs = 1,
|
|
.bufs = out_bufs,
|
|
.bufferIdentifiers = out_buf_ids,
|
|
.bufSizes = out_buf_sizes,
|
|
.bufElSizes = out_buf_el_sizes,
|
|
};
|
|
AACENC_OutArgs out_args = {};
|
|
|
|
res = aacEncEncode(this->enc, &in_buf_desc, &out_buf_desc, &in_args, &out_args);
|
|
if (res != AACENC_OK)
|
|
return -EINVAL;
|
|
|
|
write_frame_header(hdr, this->enc_seq++, out_args.numOutBytes);
|
|
|
|
*dst_out = out_args.numOutBytes + sizeof(*hdr);
|
|
|
|
return out_args.numInSamples * SAMPLE_SIZE;
|
|
}
|
|
|
|
static int codec_encode(void *data,
|
|
const void *src, size_t src_size,
|
|
void *dst, size_t dst_size,
|
|
size_t *dst_out, int *need_flush)
|
|
{
|
|
struct impl *this = data;
|
|
int consumed = 0;
|
|
int i, res;
|
|
|
|
*dst_out = 0;
|
|
|
|
for (i = 0; i < FRAMES_PER_PACKET; ++i) {
|
|
size_t out;
|
|
|
|
res = encode_frame(this, src, src_size, dst, dst_size, &out);
|
|
if (res < 0)
|
|
return res;
|
|
|
|
src = SPA_PTROFF(src, res, void);
|
|
src_size -= res;
|
|
consumed += res;
|
|
|
|
dst = SPA_PTROFF(dst, out, void);
|
|
dst_size -= out;
|
|
*dst_out += out;
|
|
}
|
|
|
|
*need_flush = NEED_FLUSH_ALL;
|
|
|
|
return consumed;
|
|
}
|
|
|
|
static int codec_start_decode (void *data,
|
|
const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp)
|
|
{
|
|
const struct rtp_header *header = src;
|
|
|
|
if (src_size <= sizeof(struct rtp_header))
|
|
return -EINVAL;
|
|
|
|
if (seqnum)
|
|
*seqnum = ntohs(header->sequence_number);
|
|
if (timestamp)
|
|
*timestamp = ntohl(header->timestamp);
|
|
|
|
return sizeof(struct rtp_header);
|
|
}
|
|
|
|
static int decode_frame(struct impl *this,
|
|
const void *src, size_t src_size,
|
|
void *dst, size_t dst_size,
|
|
size_t *dst_out)
|
|
{
|
|
uint16_t seq, size;
|
|
const struct frame_header *hdr = src;
|
|
|
|
if (src_size < sizeof(*hdr))
|
|
return -EINVAL;
|
|
|
|
if (!parse_frame_header(hdr, &seq, &size))
|
|
return -EINVAL;
|
|
|
|
src_size -= sizeof(*hdr);
|
|
src = SPA_PTROFF(src, sizeof(*hdr), void);
|
|
|
|
uint data_size = SPA_MIN(src_size, size);
|
|
uint bytes_valid = data_size;
|
|
CStreamInfo *aacinf;
|
|
int res;
|
|
|
|
res = aacDecoder_Fill(this->dec, (UCHAR **)&src, &data_size, &bytes_valid);
|
|
if (res != AAC_DEC_OK) {
|
|
spa_log_debug(global_log, "AAC buffer fill error: 0x%04X", res);
|
|
return -EINVAL;
|
|
}
|
|
|
|
res = aacDecoder_DecodeFrame(this->dec, dst, dst_size, 0);
|
|
if (res != AAC_DEC_OK) {
|
|
spa_log_debug(global_log, "AAC decode frame error: 0x%04X", res);
|
|
return -EINVAL;
|
|
}
|
|
|
|
aacinf = aacDecoder_GetStreamInfo(this->dec);
|
|
if (!aacinf) {
|
|
spa_log_debug(global_log, "AAC get stream info failed");
|
|
return -EINVAL;
|
|
}
|
|
*dst_out = aacinf->frameSize * aacinf->numChannels * SAMPLE_SIZE;
|
|
|
|
spa_assert_se(data_size >= bytes_valid);
|
|
|
|
return data_size - bytes_valid + sizeof(*hdr);
|
|
}
|
|
|
|
static int codec_decode(void *data,
|
|
const void *src, size_t src_size,
|
|
void *dst, size_t dst_size,
|
|
size_t *dst_out)
|
|
{
|
|
struct impl *this = data;
|
|
int consumed = 0;
|
|
int res;
|
|
|
|
*dst_out = 0;
|
|
|
|
while (src_size) {
|
|
size_t out;
|
|
|
|
res = decode_frame(this, src, src_size, dst, dst_size, &out);
|
|
if (res < 0)
|
|
return res;
|
|
|
|
src = SPA_PTROFF(src, res, void);
|
|
src_size -= res;
|
|
consumed += res;
|
|
|
|
dst = SPA_PTROFF(dst, out, void);
|
|
dst_size -= out;
|
|
*dst_out += out;
|
|
}
|
|
|
|
return consumed;
|
|
}
|
|
|
|
static void codec_get_delay(void *data, uint32_t *encoder, uint32_t *decoder)
|
|
{
|
|
struct impl *this = data;
|
|
|
|
if (encoder)
|
|
*encoder = this->enc_delay;
|
|
|
|
if (decoder) {
|
|
CStreamInfo *info = aacDecoder_GetStreamInfo(this->dec);
|
|
if (info)
|
|
this->dec_delay = info->outputDelay;
|
|
*decoder = this->dec_delay;
|
|
}
|
|
}
|
|
|
|
static void codec_set_log(struct spa_log *log_)
|
|
{
|
|
global_log = log_;
|
|
spa_log_topic_init(global_log, &codec_plugin_log_topic);
|
|
}
|
|
|
|
const struct media_codec a2dp_codec_aac_eld_a = {
|
|
.id = SPA_BLUETOOTH_AUDIO_CODEC_AAC_ELD_A,
|
|
.kind = MEDIA_CODEC_A2DP,
|
|
.codec_id = A2DP_CODEC_VENDOR,
|
|
.vendor = { .vendor_id = AAC_ELD_A_VENDOR_ID,
|
|
.codec_id = AAC_ELD_A_CODEC_ID },
|
|
.name = "aac_eld_a",
|
|
.description = "AAC-ELD-A",
|
|
.fill_caps = codec_fill_caps,
|
|
.select_config = codec_select_config,
|
|
.enum_config = codec_enum_config,
|
|
.validate_config = codec_validate_config,
|
|
.init = codec_init,
|
|
.deinit = codec_deinit,
|
|
.get_block_size = codec_get_block_size,
|
|
.start_encode = codec_start_encode,
|
|
.encode = codec_encode,
|
|
.start_decode = codec_start_decode,
|
|
.decode = codec_decode,
|
|
.set_log = codec_set_log,
|
|
.get_delay = codec_get_delay,
|
|
};
|
|
|
|
MEDIA_CODEC_EXPORT_DEF(
|
|
"aac-eld-a",
|
|
&a2dp_codec_aac_eld_a
|
|
);
|