| /* |
| * LC3 encoder wrapper |
| * Copyright (C) 2024 Antoine Soulier <asoulier@google.com> |
| * |
| * This file is part of FFmpeg. |
| * |
| * Permission to use, copy, modify, and/or distribute this software for any |
| * purpose with or without fee is hereby granted, provided that the above |
| * copyright notice and this permission notice appear in all copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| */ |
| |
| #include <lc3.h> |
| |
| #include "libavutil/intreadwrite.h" |
| #include "libavutil/opt.h" |
| #include "libavutil/mem.h" |
| |
| #include "avcodec.h" |
| #include "codec.h" |
| #include "codec_internal.h" |
| #include "encode.h" |
| |
| #define ENCODER_MAX_CHANNELS 2 |
| |
| typedef struct LibLC3EncOpts { |
| float frame_duration; |
| int hr_mode; |
| } LibLC3EncOpts; |
| |
| typedef struct LibLC3EncContext { |
| const AVClass *av_class; |
| LibLC3EncOpts opts; |
| int block_bytes; |
| void *encoder_mem; |
| lc3_encoder_t encoder[ENCODER_MAX_CHANNELS]; |
| int delay_samples; |
| int remaining_samples; |
| } LibLC3EncContext; |
| |
| static av_cold int liblc3_encode_init(AVCodecContext *avctx) |
| { |
| LibLC3EncContext *liblc3 = avctx->priv_data; |
| bool hr_mode = liblc3->opts.hr_mode; |
| int frame_us = liblc3->opts.frame_duration * 1000; |
| int srate_hz = avctx->sample_rate; |
| int channels = avctx->ch_layout.nb_channels; |
| int effective_bit_rate; |
| unsigned encoder_size; |
| |
| if (frame_us != 2500 && frame_us != 5000 && |
| frame_us != 7500 && frame_us != 10000 ) { |
| av_log(avctx, AV_LOG_ERROR, |
| "Unsupported frame duration %.1f ms.\n", frame_us / 1000.f); |
| return AVERROR(EINVAL); |
| } |
| if (channels < 0 || channels > ENCODER_MAX_CHANNELS) { |
| av_log(avctx, AV_LOG_ERROR, |
| "Invalid number of channels %d. Max %d channels are accepted\n", |
| channels, ENCODER_MAX_CHANNELS); |
| return AVERROR(EINVAL); |
| } |
| |
| hr_mode |= srate_hz > 48000; |
| hr_mode &= srate_hz >= 48000; |
| |
| if (frame_us == 7500 && hr_mode) { |
| av_log(avctx, AV_LOG_ERROR, |
| "High-resolution mode is not supported with 7.5 ms frames.\n"); |
| return AVERROR(EINVAL); |
| } |
| |
| av_log(avctx, AV_LOG_INFO, "Encoding %.1f ms frames.\n", frame_us / 1000.f); |
| if (hr_mode) |
| av_log(avctx, AV_LOG_INFO, "High-resolution mode is enabled.\n"); |
| |
| liblc3->block_bytes = lc3_hr_frame_block_bytes( |
| hr_mode, frame_us, srate_hz, channels, avctx->bit_rate); |
| |
| effective_bit_rate = lc3_hr_resolve_bitrate( |
| hr_mode, frame_us, srate_hz, liblc3->block_bytes); |
| |
| if (avctx->bit_rate != effective_bit_rate) |
| av_log(avctx, AV_LOG_WARNING, |
| "Bitrate changed to %d bps.\n", effective_bit_rate); |
| avctx->bit_rate = effective_bit_rate; |
| |
| encoder_size = lc3_hr_encoder_size(hr_mode, frame_us, srate_hz); |
| if (!encoder_size) |
| return AVERROR(EINVAL); |
| |
| liblc3->encoder_mem = av_malloc_array(channels, encoder_size); |
| if (!liblc3->encoder_mem) |
| return AVERROR(ENOMEM); |
| |
| for (int ch = 0; ch < channels; ch++) { |
| liblc3->encoder[ch] = lc3_hr_setup_encoder( |
| hr_mode, frame_us, srate_hz, 0, |
| (char *)liblc3->encoder_mem + ch * encoder_size); |
| } |
| |
| avctx->extradata = av_mallocz(6 + AV_INPUT_BUFFER_PADDING_SIZE); |
| if (!avctx->extradata) |
| return AVERROR(ENOMEM); |
| |
| AV_WL16(avctx->extradata + 0, frame_us / 10); |
| AV_WL16(avctx->extradata + 2, 0); |
| AV_WL16(avctx->extradata + 4, hr_mode); |
| avctx->extradata_size = 6; |
| |
| avctx->frame_size = av_rescale(frame_us, srate_hz, 1000*1000); |
| liblc3->delay_samples = lc3_hr_delay_samples(hr_mode, frame_us, srate_hz); |
| liblc3->remaining_samples = 0; |
| |
| return 0; |
| } |
| |
| static av_cold int liblc3_encode_close(AVCodecContext *avctx) |
| { |
| LibLC3EncContext *liblc3 = avctx->priv_data; |
| |
| av_freep(&liblc3->encoder_mem); |
| |
| return 0; |
| } |
| |
| static int liblc3_encode(AVCodecContext *avctx, AVPacket *pkt, |
| const AVFrame *frame, int *got_packet_ptr) |
| { |
| LibLC3EncContext *liblc3 = avctx->priv_data; |
| int block_bytes = liblc3->block_bytes; |
| int channels = avctx->ch_layout.nb_channels; |
| void *zero_frame = NULL; |
| uint8_t *data_ptr; |
| int ret; |
| |
| if ((ret = ff_get_encode_buffer(avctx, pkt, block_bytes, 0)) < 0) |
| return ret; |
| |
| if (frame) { |
| int padding = frame->nb_samples - frame->duration; |
| liblc3->remaining_samples = FFMAX(liblc3->delay_samples - padding, 0); |
| } else { |
| if (!liblc3->remaining_samples) |
| return 0; |
| |
| liblc3->remaining_samples = 0; |
| zero_frame = av_mallocz(avctx->frame_size * sizeof(float)); |
| if (!zero_frame) |
| return AVERROR(ENOMEM); |
| } |
| |
| data_ptr = pkt->data; |
| for (int ch = 0; ch < channels; ch++) { |
| const float *pcm = zero_frame ? zero_frame : frame->data[ch]; |
| int nbytes = block_bytes / channels + (ch < block_bytes % channels); |
| |
| lc3_encode(liblc3->encoder[ch], |
| LC3_PCM_FORMAT_FLOAT, pcm, 1, nbytes, data_ptr); |
| |
| data_ptr += nbytes; |
| } |
| |
| if (zero_frame) |
| av_free(zero_frame); |
| |
| *got_packet_ptr = 1; |
| |
| return 0; |
| } |
| |
| #define OFFSET(x) offsetof(LibLC3EncContext, opts.x) |
| #define FLAGS AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_ENCODING_PARAM |
| static const AVOption options[] = { |
| { "frame_duration", "Duration of a frame in milliseconds", |
| OFFSET(frame_duration), AV_OPT_TYPE_FLOAT, |
| { .dbl = 10.0 }, 2.5, 10.0, FLAGS }, |
| { "high_resolution", "Enable High-Resolution mode (48 KHz or 96 KHz)", |
| OFFSET(hr_mode), AV_OPT_TYPE_BOOL, |
| { .i64 = 0 }, 0, 1, FLAGS }, |
| { NULL } |
| }; |
| |
| static const AVClass class = { |
| .class_name = "liblc3 encoder", |
| .item_name = av_default_item_name, |
| .option = options, |
| .version = LIBAVUTIL_VERSION_INT, |
| }; |
| |
| const FFCodec ff_liblc3_encoder = { |
| .p.name = "liblc3", |
| CODEC_LONG_NAME("LC3 (Low Complexity Communication Codec)"), |
| .p.type = AVMEDIA_TYPE_AUDIO, |
| .p.id = AV_CODEC_ID_LC3, |
| .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY, |
| .p.supported_samplerates = (const int []) |
| { 96000, 48000, 32000, 24000, 16000, 8000, 0 }, |
| .p.sample_fmts = (const enum AVSampleFormat[]) |
| { AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_NONE }, |
| .p.priv_class = &class, |
| .p.wrapper_name = "liblc3", |
| .priv_data_size = sizeof(LibLC3EncContext), |
| .init = liblc3_encode_init, |
| .close = liblc3_encode_close, |
| FF_CODEC_ENCODE_CB(liblc3_encode), |
| }; |