From 392fcda01f5af720a74a1a1bb2c43d07ad0f4abc Mon Sep 17 00:00:00 2001 From: Gabriel Ebner Date: Tue, 22 Dec 2020 12:39:56 +0100 Subject: [PATCH] a2dp: add aac encoder --- spa/meson.build | 1 + spa/plugins/bluez5/a2dp-codec-aac.c | 326 +++++++++++++++++++++++++--- spa/plugins/bluez5/meson.build | 6 +- 3 files changed, 302 insertions(+), 31 deletions(-) diff --git a/spa/meson.build b/spa/meson.build index 31eef86ed..47fa19f93 100644 --- a/spa/meson.build +++ b/spa/meson.build @@ -31,6 +31,7 @@ if get_option('spa-plugins') ldac_dep = dependency('ldacBT-enc', required : false) ldac_abr_dep = dependency('ldacBT-abr', required : false) aptx_dep = dependency('libopenaptx', required : false) + fdk_aac_dep = dependency('fdk-aac', required : false) endif if get_option('ffmpeg') avcodec_dep = dependency('libavcodec') diff --git a/spa/plugins/bluez5/a2dp-codec-aac.c b/spa/plugins/bluez5/a2dp-codec-aac.c index 33b3601a3..cade782f7 100644 --- a/spa/plugins/bluez5/a2dp-codec-aac.c +++ b/spa/plugins/bluez5/a2dp-codec-aac.c @@ -29,17 +29,26 @@ #include +#include + #include "defs.h" #include "rtp.h" #include "a2dp-codecs.h" struct impl { + HANDLE_AACENCODER aacenc; + struct rtp_header *header; - struct rtp_payload *payload; size_t mtu; int codesize; - int frame_length; + + int max_bitrate; + int cur_bitrate; + + uint32_t rate; + uint32_t channels; + int samplesize; }; static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, @@ -68,18 +77,37 @@ static int codec_fill_caps(const struct a2dp_codec *codec, uint32_t flags, AAC_CHANNELS_1 | AAC_CHANNELS_2, .vbr = 1, - AAC_INIT_BITRATE(0xFFFF) + AAC_INIT_BITRATE(0xFFFFF) }; memcpy(caps, &a2dp_aac, sizeof(a2dp_aac)); return sizeof(a2dp_aac); } +static struct { + int config; + int freq; +} aac_frequencies[] = { + { AAC_SAMPLING_FREQ_48000, 48000 }, + { AAC_SAMPLING_FREQ_44100, 44100 }, + { AAC_SAMPLING_FREQ_96000, 96000 }, + { AAC_SAMPLING_FREQ_88200, 88200 }, + { AAC_SAMPLING_FREQ_64000, 64000 }, + { AAC_SAMPLING_FREQ_32000, 32000 }, + { AAC_SAMPLING_FREQ_24000, 24000 }, + { AAC_SAMPLING_FREQ_22050, 22050 }, + { AAC_SAMPLING_FREQ_16000, 16000 }, + { AAC_SAMPLING_FREQ_12000, 12000 }, + { AAC_SAMPLING_FREQ_11025, 11025 }, + { AAC_SAMPLING_FREQ_8000, 8000 }, +}; + static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, const void *caps, size_t caps_size, const struct spa_audio_info *info, uint8_t config[A2DP_MAX_CAPS_SIZE]) { a2dp_aac_t conf; int freq; + bool freq_found; if (caps_size < sizeof(conf)) return -EINVAL; @@ -98,35 +126,17 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return -ENOTSUP; freq = AAC_GET_FREQUENCY(conf); - if (freq & AAC_SAMPLING_FREQ_48000) - freq = AAC_SAMPLING_FREQ_48000; - else if (freq & AAC_SAMPLING_FREQ_44100) - freq = AAC_SAMPLING_FREQ_44100; - else if (freq & AAC_SAMPLING_FREQ_64000) - freq = AAC_SAMPLING_FREQ_64000; - else if (freq & AAC_SAMPLING_FREQ_32000) - freq = AAC_SAMPLING_FREQ_32000; - else if (freq & AAC_SAMPLING_FREQ_88200) - freq = AAC_SAMPLING_FREQ_88200; - else if (freq & AAC_SAMPLING_FREQ_96000) - freq = AAC_SAMPLING_FREQ_96000; - else if (freq & AAC_SAMPLING_FREQ_24000) - freq = AAC_SAMPLING_FREQ_24000; - else if (freq & AAC_SAMPLING_FREQ_22050) - freq = AAC_SAMPLING_FREQ_22050; - else if (freq & AAC_SAMPLING_FREQ_16000) - freq = AAC_SAMPLING_FREQ_16000; - else if (freq & AAC_SAMPLING_FREQ_12000) - freq = AAC_SAMPLING_FREQ_12000; - else if (freq & AAC_SAMPLING_FREQ_11025) - freq = AAC_SAMPLING_FREQ_11025; - else if (freq & AAC_SAMPLING_FREQ_8000) - freq = AAC_SAMPLING_FREQ_8000; - else + freq_found = false; + for (size_t i = 0; i < SPA_N_ELEMENTS(aac_frequencies); i++) { + if (freq & aac_frequencies[i].config) { + AAC_SET_FREQUENCY(conf, aac_frequencies[i].config); + freq_found = true; + break; + } + } + if (!freq_found) return -ENOTSUP; - AAC_SET_FREQUENCY(conf, freq); - if (conf.channels & AAC_CHANNELS_2) conf.channels = AAC_CHANNELS_2; else if (conf.channels & AAC_CHANNELS_1) @@ -139,10 +149,80 @@ static int codec_select_config(const struct a2dp_codec *codec, uint32_t flags, return sizeof(conf); } +static int codec_enum_config(const struct a2dp_codec *codec, + const void *caps, size_t caps_size, uint32_t id, uint32_t idx, + struct spa_pod_builder *b, struct spa_pod **param) +{ + a2dp_aac_t conf; + struct spa_pod_frame f[2]; + struct spa_pod_choice *choice; + uint32_t position[SPA_AUDIO_MAX_CHANNELS]; + uint32_t i = 0; + + if (caps_size < sizeof(conf)) + return -EINVAL; + + memcpy(&conf, caps, sizeof(conf)); + + 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(SPA_MEDIA_TYPE_audio), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S16), + 0); + spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0); + + spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0); + choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]); + i = 0; + for (size_t j = 0; j < SPA_N_ELEMENTS(aac_frequencies); j++) { + if (AAC_GET_FREQUENCY(conf) & aac_frequencies[j].config) { + if (i++ == 0) + spa_pod_builder_int(b, aac_frequencies[j].freq); + spa_pod_builder_int(b, aac_frequencies[j].freq); + } + } + if (i == 0) + return -EINVAL; + if (i > 1) + choice->body.type = SPA_CHOICE_Enum; + spa_pod_builder_pop(b, &f[1]); + + + if (SPA_FLAG_IS_SET(conf.channels, AAC_CHANNELS_1 | AAC_CHANNELS_2)) { + spa_pod_builder_add(b, + SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(2, 1, 2), + 0); + } else if (conf.channels & AAC_CHANNELS_1) { + position[0] = SPA_AUDIO_CHANNEL_MONO; + spa_pod_builder_add(b, + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(1), + SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), + SPA_TYPE_Id, 1, position), + 0); + } else if (conf.channels & AAC_CHANNELS_2) { + position[0] = SPA_AUDIO_CHANNEL_FL; + position[1] = SPA_AUDIO_CHANNEL_FR; + spa_pod_builder_add(b, + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(2), + SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), + SPA_TYPE_Id, 2, position), + 0); + } else + return -EINVAL; + + *param = spa_pod_builder_pop(b, &f[0]); + return *param == NULL ? -EIO : 1; +} + static void *codec_init(const struct a2dp_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, size_t mtu) { struct impl *this; + a2dp_aac_t *conf = config; int res; this = calloc(1, sizeof(struct impl)); @@ -151,9 +231,77 @@ static void *codec_init(const struct a2dp_codec *codec, uint32_t flags, goto error; } this->mtu = mtu; + this->rate = info->info.raw.rate; + this->channels = info->info.raw.channels; + + 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; + } + this->samplesize = 2; + + res = aacEncOpen(&this->aacenc, 0, this->channels); + if (res != AACENC_OK) + goto error; + + if (conf->object_type != AAC_OBJECT_TYPE_MPEG2_AAC_LC && + conf->object_type != AAC_OBJECT_TYPE_MPEG4_AAC_LC) { + res = -EINVAL; + goto error; + } + res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_AAC_LC); + if (res != AACENC_OK) + goto error; + + res = aacEncoder_SetParam(this->aacenc, AACENC_SAMPLERATE, this->rate); + if (res != AACENC_OK) + goto error; + + res = aacEncoder_SetParam(this->aacenc, AACENC_CHANNELMODE, this->channels); + if (res != AACENC_OK) + goto error; + + // Fragmentation is not implemented yet, + // so make sure every encoded AAC frame fits in (mtu - header) + this->max_bitrate = ((this->mtu - sizeof(struct rtp_header)) * 8 * this->rate) / 1024; + this->max_bitrate = SPA_MIN(this->max_bitrate, AAC_GET_BITRATE(*conf)); + this->cur_bitrate = this->max_bitrate; + + res = aacEncoder_SetParam(this->aacenc, AACENC_BITRATE, this->cur_bitrate); + if (res != AACENC_OK) + goto error; + + res = aacEncoder_SetParam(this->aacenc, AACENC_PEAK_BITRATE, this->max_bitrate); + if (res != AACENC_OK) + goto error; + + res = aacEncoder_SetParam(this->aacenc, AACENC_TRANSMUX, TT_MP4_LATM_MCP1); + if (res != AACENC_OK) + goto error; + + res = aacEncoder_SetParam(this->aacenc, AACENC_AFTERBURNER, 1); + if (res != AACENC_OK) + goto error; + + res = aacEncEncode(this->aacenc, NULL, NULL, NULL, NULL); + if (res != AACENC_OK) + goto error; + + AACENC_InfoStruct enc_info = {}; + res = aacEncInfo(this->aacenc, &enc_info); + if (res != AACENC_OK) + goto error; + + this->codesize = enc_info.frameLength * this->channels * this->samplesize; return this; + error: + if (this->aacenc) + aacEncClose(&this->aacenc); + free(this); errno = -res; return NULL; } @@ -161,15 +309,133 @@ error: static void codec_deinit(void *data) { struct impl *this = data; + if (this->aacenc) + aacEncClose(&this->aacenc); free(this); } +static int codec_get_num_blocks(void *data) +{ + return 1; +} + +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 impl *this = data; + + this->header = (struct rtp_header *)dst; + memset(this->header, 0, sizeof(struct rtp_header)); + + this->header->v = 2; + this->header->pt = 96; + this->header->sequence_number = htons(seqnum); + this->header->timestamp = htonl(timestamp); + this->header->ssrc = htonl(1); + return sizeof(struct rtp_header); +} + +static int codec_encode(void *data, + const void *src, size_t src_size, + void *dst, size_t dst_size, + size_t *dst_out) +{ + struct impl *this = data; + int res; + + void *in_bufs[] = {(void *) src}; + int in_buf_ids[] = {IN_AUDIO_DATA}; + int in_buf_sizes[] = {src_size}; + int in_buf_el_sizes[] = {this->samplesize}; + 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 / this->samplesize, + }; + + void *out_bufs[] = {dst}; + int out_buf_ids[] = {OUT_BITSTREAM_DATA}; + int out_buf_sizes[] = {dst_size}; + int out_buf_el_sizes[] = {this->samplesize}; + 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->aacenc, &in_buf_desc, &out_buf_desc, &in_args, &out_args); + if (res != AACENC_OK) + return -EINVAL; + + *dst_out = out_args.numOutBytes; + + return out_args.numInSamples * this->samplesize; +} + +static int codec_abr_process (void *data, size_t unsent) +{ + return -ENOTSUP; +} + +static int codec_change_bitrate(struct impl *this, int new_bitrate) +{ + int res; + + new_bitrate = SPA_MIN(new_bitrate, this->max_bitrate); + new_bitrate = SPA_MAX(new_bitrate, 64000); + + if (new_bitrate == this->cur_bitrate) + return 0; + + this->cur_bitrate = new_bitrate; + + res = aacEncoder_SetParam(this->aacenc, AACENC_BITRATE, this->cur_bitrate); + if (res != AACENC_OK) + return -EINVAL; + + return 0; +} + +static int codec_reduce_bitpool(void *data) +{ + struct impl *this = data; + return codec_change_bitrate(this, (this->cur_bitrate * 2) / 3); +} + +static int codec_increase_bitpool(void *data) +{ + struct impl *this = data; + return codec_change_bitrate(this, (this->cur_bitrate * 4) / 3); +} + const struct a2dp_codec a2dp_codec_aac = { .codec_id = A2DP_CODEC_MPEG24, .name = "aac", .description = "AAC", .fill_caps = codec_fill_caps, .select_config = codec_select_config, + .enum_config = codec_enum_config, .init = codec_init, .deinit = codec_deinit, + .get_block_size = codec_get_block_size, + .get_num_blocks = codec_get_num_blocks, + .start_encode = codec_start_encode, + .encode = codec_encode, + .abr_process = codec_abr_process, + .reduce_bitpool = codec_reduce_bitpool, + .increase_bitpool = codec_increase_bitpool, }; diff --git a/spa/plugins/bluez5/meson.build b/spa/plugins/bluez5/meson.build index 7dccf92d7..07f4016b4 100644 --- a/spa/plugins/bluez5/meson.build +++ b/spa/plugins/bluez5/meson.build @@ -1,7 +1,6 @@ bluez5_sources = ['plugin.c', 'a2dp-codecs.c', - 'a2dp-codec-aac.c', 'a2dp-codec-sbc.c', 'a2dp-sink.c', 'a2dp-source.c', @@ -27,6 +26,11 @@ if aptx_dep.found() bluez5_args += [ '-DENABLE_APTX' ] bluez5_deps += aptx_dep endif +if fdk_aac_dep.found() + bluez5_sources += [ 'a2dp-codec-aac.c' ] + bluez5_args += [ '-DENABLE_AAC' ] + bluez5_deps += fdk_aac_dep +endif if get_option('bluez5-backend-native') bluez5_sources += ['backend-hsp-native.c']