mirror of https://github.com/darlinghq/darling
797 lines
22 KiB
C++
797 lines
22 KiB
C++
/*
|
|
This file is part of Darling.
|
|
|
|
Copyright (C) 2020 Lubos Dolezel
|
|
|
|
Darling is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Darling 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 General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Darling. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "AudioConverterImpl.h"
|
|
#include <CarbonCore/MacErrors.h>
|
|
#include <stdexcept>
|
|
#include <cstring>
|
|
#include <cassert>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include "stub.h"
|
|
|
|
extern "C" {
|
|
#include <libavcodec/avcodec.h>
|
|
#include <libavutil/opt.h>
|
|
#include <libavutil/mem.h>
|
|
}
|
|
|
|
static constexpr int ENCODER_FRAME_SAMPLES = 1024;
|
|
|
|
// http://blinkingblip.wordpress.com/
|
|
|
|
static void throwFFMPEGError(int errnum, const char* function)
|
|
{
|
|
char buf[256];
|
|
std::stringstream ss;
|
|
|
|
if (av_strerror(errnum, buf, sizeof(buf)) == 0)
|
|
ss << function << ": " << buf;
|
|
else
|
|
ss << function << ": unknown error";
|
|
|
|
throw std::runtime_error(ss.str());
|
|
}
|
|
|
|
AudioConverter::AudioConverter(const AudioStreamBasicDescription* inSourceFormat, const AudioStreamBasicDescription* inDestinationFormat)
|
|
: m_sourceFormat(*inSourceFormat), m_destinationFormat(*inDestinationFormat), m_decoder(nullptr), m_encoder(nullptr)
|
|
{
|
|
memset(&m_avpkt, 0, sizeof(m_avpkt));
|
|
memset(&m_avpktOut, 0, sizeof(m_avpktOut));
|
|
}
|
|
|
|
void AudioConverter::flush()
|
|
{
|
|
TRACE();
|
|
avcodec_flush_buffers(m_decoder);
|
|
avcodec_flush_buffers(m_encoder);
|
|
//avcodec_close(m_encoder);
|
|
}
|
|
|
|
OSStatus AudioConverter::create(const AudioStreamBasicDescription* inSourceFormat, const AudioStreamBasicDescription* inDestinationFormat, AudioConverter** out)
|
|
{
|
|
TRACE2(inSourceFormat, inDestinationFormat);
|
|
|
|
// TODO: non-interleaved audio
|
|
|
|
const AVCodec *codecIn, *codecOut;
|
|
AVCodecContext *cIn;
|
|
AVCodecContext *cOut;
|
|
enum AVCodecID idIn, idOut;
|
|
|
|
*out = nullptr;
|
|
idIn = CACodecToAV(inSourceFormat);
|
|
idOut = CACodecToAV(inDestinationFormat);
|
|
|
|
if (idIn == AV_CODEC_ID_NONE || idOut == AV_CODEC_ID_NONE)
|
|
{
|
|
// LOG << "AudioConverter::create(): Unsupported codec, format in = " << std::hex << inSourceFormat->mFormatID << ", out = " << inDestinationFormat->mFormatID << std::dec << std::endl;
|
|
return paramErr;
|
|
}
|
|
|
|
codecIn = avcodec_find_decoder(idIn);
|
|
codecOut = avcodec_find_encoder(idOut);
|
|
|
|
if (!codecIn || !codecOut)
|
|
{
|
|
// LOG << "AudioConverter::create(): avcodec_find_*() failed, format in = " << std::hex << inSourceFormat->mFormatID << ", out = " << inDestinationFormat->mFormatID << std::dec << std::endl;
|
|
return paramErr;
|
|
}
|
|
|
|
*out = new AudioConverter(inSourceFormat, inDestinationFormat);
|
|
|
|
(*out)->m_decoder = cIn = avcodec_alloc_context3(codecIn);
|
|
|
|
if (inSourceFormat->mFormatID == kAudioFormatLinearPCM)
|
|
{
|
|
#warning "TODO: Remove deprecated 'channels' once we no longer support older distros"
|
|
#if LIBAVCODEC_VERSION_MAJOR >= 61
|
|
cIn->ch_layout.nb_channels = inSourceFormat->mChannelsPerFrame;
|
|
#else
|
|
cIn->channels = inSourceFormat->mChannelsPerFrame;
|
|
#endif
|
|
cIn->sample_rate = inSourceFormat->mSampleRate;
|
|
|
|
#if LIBAVCODEC_VERSION_MAJOR >= 61
|
|
std::cout << "Converting from PCM with " << cIn->ch_layout.nb_channels << " channels at " << cIn->sample_rate << " Hz\n";
|
|
#else
|
|
std::cout << "Converting from PCM with " << cIn->channels << " channels at " << cIn->sample_rate << " Hz\n";
|
|
#endif
|
|
}
|
|
|
|
if (avcodec_open2((*out)->m_decoder, codecIn, nullptr) < 0)
|
|
{
|
|
delete *out;
|
|
std::cerr << "AudioConverter::create(): avcodec_open() failed, format in = " << std::hex << inSourceFormat->mFormatID << ", out = " << inDestinationFormat->mFormatID << std::dec << std::endl;
|
|
|
|
return paramErr;
|
|
}
|
|
|
|
// The encoder will be initialized after we process the first packet
|
|
(*out)->m_encoder = cOut = avcodec_alloc_context3(codecOut);
|
|
(*out)->m_codecIn = codecIn;
|
|
(*out)->m_codecOut = codecOut;
|
|
|
|
return noErr;
|
|
}
|
|
|
|
void AudioConverter::initEncoder()
|
|
{
|
|
int err;
|
|
|
|
if (m_encoderInitialized)
|
|
throw std::logic_error("Encoder already initialized");
|
|
|
|
m_encoder->codec_type = AVMEDIA_TYPE_AUDIO;
|
|
m_encoder->bit_rate = m_outBitRate;
|
|
#warning "TODO: Remove deprecated 'channels' once we no longer support older distros"
|
|
#if LIBAVCODEC_VERSION_MAJOR >= 61
|
|
m_encoder->ch_layout.order = AV_CHANNEL_ORDER_UNSPEC;
|
|
m_encoder->ch_layout.nb_channels = m_destinationFormat.mChannelsPerFrame;
|
|
#else
|
|
m_encoder->channels = m_destinationFormat.mChannelsPerFrame;
|
|
m_encoder->channel_layout = CAChannelCountToLayout(m_destinationFormat.mChannelsPerFrame);
|
|
#endif
|
|
m_encoder->sample_rate = m_destinationFormat.mSampleRate;
|
|
m_encoder->sample_fmt = CACodecSampleFormat(&m_destinationFormat);
|
|
|
|
#ifdef DEBUG_AUDIOCONVERTER
|
|
std::cout << "ENCODER FORMAT:\n";
|
|
std::cout << "\tSample rate: " << m_encoder->sample_rate << std::endl;
|
|
std::cout << "\tChannels: " << m_destinationFormat.mChannelsPerFrame << std::endl;
|
|
std::cout << "\tFormat: 0x" << std::hex << m_encoder->sample_fmt << std::dec << std::endl;
|
|
#endif
|
|
|
|
err = avcodec_open2(m_encoder, m_codecOut, 0);
|
|
if (err < 0)
|
|
throwFFMPEGError(err, "avcodec_open2() encoder");
|
|
|
|
allocateBuffers();
|
|
m_encoderInitialized = true;
|
|
}
|
|
|
|
void AudioConverter::allocateBuffers()
|
|
{
|
|
#ifdef HAVE_AV_FRAME_ALLOC
|
|
m_audioFrame = av_frame_alloc();
|
|
#else
|
|
m_audioFrame = avcodec_alloc_frame();
|
|
#endif
|
|
|
|
m_audioFrame->nb_samples = ENCODER_FRAME_SAMPLES;
|
|
m_audioFrame->format = m_encoder->sample_fmt;
|
|
|
|
#warning "TODO: Remove deprecated 'channels' once we no longer support older distros"
|
|
#if LIBAVCODEC_VERSION_MAJOR >= 61
|
|
m_audioFrame->ch_layout.order = m_encoder->ch_layout.order;
|
|
m_audioFrame->ch_layout.nb_channels = m_encoder->ch_layout.nb_channels;
|
|
int audioSampleBuffer_size = av_samples_get_buffer_size(nullptr, m_encoder->ch_layout.nb_channels, m_audioFrame->nb_samples, m_encoder->sample_fmt, 0);
|
|
#else
|
|
m_audioFrame->channel_layout = m_encoder->channel_layout;
|
|
int audioSampleBuffer_size = av_samples_get_buffer_size(nullptr, m_encoder->channels, m_audioFrame->nb_samples, m_encoder->sample_fmt, 0);
|
|
#endif
|
|
void* audioSampleBuffer = (uint8_t*) av_malloc(audioSampleBuffer_size);
|
|
|
|
if (!audioSampleBuffer)
|
|
{
|
|
std::cerr << "AudioConverter::allocateBuffers(): Failed to allocate sample buffer\n";
|
|
throw std::runtime_error("AudioConverter::allocateBuffers(): Failed to allocate sample buffer");
|
|
}
|
|
|
|
// Setup the data pointers in the AVFrame
|
|
#if LIBAVCODEC_VERSION_MAJOR >= 61
|
|
if (int err = avcodec_fill_audio_frame(m_audioFrame, m_encoder->ch_layout.nb_channels, m_encoder->sample_fmt,
|
|
(const uint8_t*) audioSampleBuffer, audioSampleBuffer_size, 0 ); err < 0)
|
|
#else
|
|
if (int err = avcodec_fill_audio_frame(m_audioFrame, m_encoder->channels, m_encoder->sample_fmt,
|
|
(const uint8_t*) audioSampleBuffer, audioSampleBuffer_size, 0 ); err < 0)
|
|
#endif
|
|
{
|
|
std::cerr << "AudioConverter::allocateBuffers(): Could not set up audio frame\n";
|
|
throw std::runtime_error("AudioConverter::allocateBuffers(): Could not set up audio frame");
|
|
}
|
|
}
|
|
|
|
AudioConverter::~AudioConverter()
|
|
{
|
|
TRACE();
|
|
if (m_decoder)
|
|
avcodec_free_context(&m_decoder);
|
|
if (m_encoder)
|
|
avcodec_free_context(&m_encoder);
|
|
if (m_audioFrame)
|
|
av_free(m_audioFrame);
|
|
if (m_resampler)
|
|
swr_free(&m_resampler);
|
|
}
|
|
|
|
template <typename T> OSStatus setPropertyT(UInt32 inPropertyDataSize, T* localProperty, const void* propertySource)
|
|
{
|
|
const T* t = static_cast<const T*>(propertySource);
|
|
|
|
if (inPropertyDataSize != sizeof(T))
|
|
return kAudioConverterErr_BadPropertySizeError;
|
|
|
|
*localProperty = *t;
|
|
return noErr;
|
|
}
|
|
|
|
OSStatus AudioConverter::setProperty(AudioConverterPropertyID inPropertyID, UInt32 inPropertyDataSize, const void *inPropertyData)
|
|
{
|
|
switch (inPropertyID)
|
|
{
|
|
case kAudioConverterEncodeBitRate:
|
|
{
|
|
return setPropertyT(inPropertyDataSize, &m_outBitRate, inPropertyData);
|
|
}
|
|
case kAudioConverterInputChannelLayout:
|
|
{
|
|
}
|
|
case kAudioConverterOutputChannelLayout:
|
|
{
|
|
}
|
|
case kAudioConverterCurrentOutputStreamDescription:
|
|
{
|
|
return kAudioConverterErr_PropertyNotSupported;
|
|
//return setPropertyT(inPropertyDataSize, &m_sourceFormat, inPropertyData);
|
|
}
|
|
case kAudioConverterCurrentInputStreamDescription:
|
|
{
|
|
return kAudioConverterErr_PropertyNotSupported;
|
|
//return setPropertyT(inPropertyDataSize, &m_destinationFormat, inPropertyData);
|
|
}
|
|
default:
|
|
{
|
|
STUB();
|
|
return kAudioConverterErr_PropertyNotSupported;
|
|
}
|
|
}
|
|
|
|
return unimpErr;
|
|
}
|
|
|
|
OSStatus AudioConverter::getPropertyInfo(AudioConverterPropertyID inPropertyID, UInt32 *outSize, Boolean *outWritable)
|
|
{
|
|
STUB();
|
|
return unimpErr;
|
|
}
|
|
|
|
template <typename T> OSStatus getPropertyT(UInt32 *ioPropertyDataSize, const T* localProperty, void* propertyTarget)
|
|
{
|
|
T* t = static_cast<T*>(propertyTarget);
|
|
|
|
if (*ioPropertyDataSize < sizeof(T))
|
|
return kAudioConverterErr_BadPropertySizeError;
|
|
*ioPropertyDataSize = sizeof(T);
|
|
|
|
*t = *localProperty;
|
|
return noErr;
|
|
}
|
|
|
|
OSStatus AudioConverter::getProperty(AudioConverterPropertyID inPropertyID, UInt32 *ioPropertyDataSize, void *outPropertyData)
|
|
{
|
|
switch (inPropertyID)
|
|
{
|
|
case kAudioConverterEncodeBitRate:
|
|
{
|
|
return getPropertyT(ioPropertyDataSize, &m_outBitRate, outPropertyData);
|
|
}
|
|
case kAudioConverterCurrentInputStreamDescription:
|
|
{
|
|
return getPropertyT(ioPropertyDataSize, &m_sourceFormat, outPropertyData);
|
|
}
|
|
case kAudioConverterCurrentOutputStreamDescription:
|
|
{
|
|
return getPropertyT(ioPropertyDataSize, &m_destinationFormat, outPropertyData);
|
|
}
|
|
default:
|
|
{
|
|
STUB();
|
|
return kAudioConverterErr_PropertyNotSupported;
|
|
}
|
|
}
|
|
}
|
|
|
|
OSStatus AudioConverter::feedInput(AudioConverterComplexInputDataProc dataProc, void* opaque, UInt32& numDataPackets)
|
|
{
|
|
AudioBufferList bufferList;
|
|
AudioStreamPacketDescription* aspd;
|
|
OSStatus err;
|
|
|
|
numDataPackets = 4096; // TODO: increase this?
|
|
bufferList.mNumberBuffers = 1;
|
|
bufferList.mBuffers[0].mDataByteSize = 0;
|
|
bufferList.mBuffers[0].mData = nullptr;
|
|
|
|
err = dataProc(AudioConverterRef(this), &numDataPackets, &bufferList, &aspd, opaque);
|
|
|
|
if (err != noErr)
|
|
return err;
|
|
|
|
m_avpkt.size = bufferList.mBuffers[0].mDataByteSize;
|
|
m_avpkt.data = (uint8_t*) bufferList.mBuffers[0].mData;
|
|
|
|
return noErr;
|
|
}
|
|
|
|
void AudioConverter::setupResampler(const AVFrame* frame)
|
|
{
|
|
int err;
|
|
|
|
if (m_resampler != nullptr)
|
|
throw std::logic_error("Resampler already created");
|
|
|
|
m_resampler = swr_alloc();
|
|
m_targetFormat = CACodecSampleFormat(&m_destinationFormat);
|
|
|
|
av_opt_set_int(m_resampler, "in_channel_layout", CAChannelCountToLayout(m_sourceFormat.mChannelsPerFrame), 0);
|
|
av_opt_set_int(m_resampler, "out_channel_layout", CAChannelCountToLayout(m_destinationFormat.mChannelsPerFrame), 0);
|
|
av_opt_set_int(m_resampler, "in_channels", m_sourceFormat.mChannelsPerFrame, 0);
|
|
av_opt_set_int(m_resampler, "out_channels", m_destinationFormat.mChannelsPerFrame, 0);
|
|
av_opt_set_int(m_resampler, "in_sample_rate", frame->sample_rate, 0);
|
|
av_opt_set_int(m_resampler, "out_sample_rate", m_destinationFormat.mSampleRate, 0);
|
|
av_opt_set_int(m_resampler, "in_sample_fmt", frame->format, 0);
|
|
av_opt_set_int(m_resampler, "out_sample_fmt", m_targetFormat, 0);
|
|
|
|
#ifdef DEBUG_AUDIOCONVERTER
|
|
std::cout << "RESAMPLER:\n";
|
|
std::cout << "\tInput rate: " << frame->sample_rate << std::endl;
|
|
std::cout << "\tInput format: 0x" << std::hex << frame->format << std::dec <<std::endl;
|
|
std::cout << "\tOutput rate: " << m_destinationFormat.mSampleRate << std::endl;
|
|
std::cout << "\tOutput format: 0x" << std::hex << CACodecSampleFormat(&m_destinationFormat) << std::dec << std::endl;
|
|
|
|
m_resamplerInput.open("/tmp/resampler.in.raw", std::ios_base::binary | std::ios_base::out);
|
|
m_resamplerOutput.open("/tmp/resampler.out.raw", std::ios_base::binary | std::ios_base::out);
|
|
m_encoderOutput.open("/tmp/encoder.out.raw", std::ios_base::binary | std::ios_base::out);
|
|
#endif
|
|
|
|
err = swr_init(m_resampler);
|
|
if (err < 0)
|
|
throwFFMPEGError(err, "swr_init()");
|
|
}
|
|
|
|
OSStatus AudioConverter::fillComplex(AudioConverterComplexInputDataProc dataProc, void* opaque,
|
|
UInt32* ioOutputDataPacketSize, AudioBufferList *outOutputData, AudioStreamPacketDescription* outPacketDescription)
|
|
{
|
|
AVFrame* srcaudio;
|
|
|
|
#ifdef HAVE_AV_FRAME_ALLOC
|
|
srcaudio = av_frame_alloc();
|
|
av_frame_unref(srcaudio);
|
|
#else
|
|
srcaudio = avcodec_alloc_frame();
|
|
avcodec_get_frame_defaults(srcaudio);
|
|
#endif
|
|
|
|
try
|
|
{
|
|
for (uint32_t i = 0; i < outOutputData->mNumberBuffers; i++)
|
|
{
|
|
UInt32 origSize = outOutputData->mBuffers[i].mDataByteSize;
|
|
UInt32& newSize = outOutputData->mBuffers[i].mDataByteSize;
|
|
|
|
newSize = 0;
|
|
|
|
while (newSize < origSize)
|
|
{
|
|
if (m_avpktOutUsed < m_avpktOut.size)
|
|
{
|
|
// std::cout << "case 1 (used " << m_avpktOutUsed << " from " << m_avpktOut.size << ")\n";
|
|
// Feed output from previous conversion
|
|
while (m_avpktOutUsed < m_avpktOut.size && newSize < origSize)
|
|
{
|
|
// Output data
|
|
int tocopy = std::min<int>(m_avpktOut.size - m_avpktOutUsed, origSize - newSize);
|
|
memcpy(((char*) outOutputData->mBuffers[i].mData) + newSize, m_avpktOut.data + m_avpktOutUsed, tocopy);
|
|
newSize += tocopy;
|
|
m_avpktOutUsed += tocopy;
|
|
}
|
|
|
|
if (m_avpktOutUsed >= m_avpktOut.size)
|
|
{
|
|
m_avpktOutUsed = 0;
|
|
av_packet_unref(&m_avpktOut);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (!feedEncoder())
|
|
{
|
|
if (!feedDecoder(dataProc, opaque, srcaudio))
|
|
goto end;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
end:
|
|
|
|
#ifdef HAVE_AV_FRAME_ALLOC
|
|
av_frame_free(&srcaudio);
|
|
#else
|
|
avcodec_free_frame(&srcaudio);
|
|
#endif
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
std::cerr << "AudioConverter::fillComplex(): Exception: " << e.what();
|
|
#ifdef HAVE_AV_FRAME_ALLOC
|
|
av_frame_free(&srcaudio);
|
|
#else
|
|
avcodec_free_frame(&srcaudio);
|
|
#endif
|
|
return ioErr;
|
|
}
|
|
catch (OSStatus err)
|
|
{
|
|
std::cerr << "AudioConverter::fillComplex(): OSStatus error: " << err;
|
|
#ifdef HAVE_AV_FRAME_ALLOC
|
|
av_frame_free(&srcaudio);
|
|
#else
|
|
avcodec_free_frame(&srcaudio);
|
|
#endif
|
|
return err;
|
|
}
|
|
|
|
return noErr;
|
|
}
|
|
|
|
bool AudioConverter::feedDecoder(AudioConverterComplexInputDataProc dataProc, void* opaque, AVFrame* srcaudio)
|
|
{
|
|
int gotFrame, err;
|
|
|
|
do
|
|
{
|
|
// Read input
|
|
if (!m_avpkt.size)
|
|
{
|
|
UInt32 numDataPackets = 0;
|
|
OSStatus err = feedInput(dataProc, opaque, numDataPackets);
|
|
|
|
// The documentation says that this may be a temporary condition
|
|
if (err != noErr)
|
|
return false;
|
|
|
|
if (!m_avpkt.size) // numDataPackets cannot be trusted
|
|
return false;
|
|
}
|
|
|
|
#if 0
|
|
err = avcodec_decode_audio4(m_decoder, srcaudio, &gotFrame, &m_avpkt);
|
|
if (err < 0)
|
|
throwFFMPEGError(err, "avcodec_decode_audio4()");
|
|
|
|
m_avpkt.size -= err;
|
|
m_avpkt.data += err;
|
|
#else
|
|
#warning TODO: test this new avcodec decoder code
|
|
err = avcodec_send_packet(m_decoder, &m_avpkt);
|
|
|
|
if (err < 0) {
|
|
if (err == AVERROR(EAGAIN)) {
|
|
// we need to consume frames before sending more packets
|
|
err = 0;
|
|
}
|
|
if (err < 0) {
|
|
throwFFMPEGError(err, "avcodec_send_packet()");
|
|
}
|
|
} else {
|
|
// on success, the data packet has been consumed entirely
|
|
m_avpkt.data += m_avpkt.size;
|
|
m_avpkt.size = 0;
|
|
}
|
|
|
|
err = avcodec_receive_frame(m_decoder, srcaudio);
|
|
|
|
if (err < 0) {
|
|
gotFrame = false;
|
|
if (err == AVERROR(EAGAIN)) {
|
|
// we need to send more packets before consuming a frame
|
|
err = 0;
|
|
}
|
|
if (err < 0) {
|
|
throwFFMPEGError(err, "avcodec_receive_frame()");
|
|
}
|
|
} else {
|
|
// on success, we have a valid frame
|
|
gotFrame = true;
|
|
}
|
|
#endif
|
|
|
|
if (gotFrame)
|
|
{
|
|
if (!m_resampler)
|
|
setupResampler(srcaudio);
|
|
|
|
#ifdef DEBUG_AUDIOCONVERTER
|
|
m_resamplerInput.write((char*) srcaudio->data, srcaudio->nb_samples * 2 * m_sourceFormat.mChannelsPerFrame);
|
|
m_resamplerInput.flush();
|
|
#endif
|
|
|
|
// Resample PCM
|
|
err = swr_convert(m_resampler, nullptr, 0, (const uint8_t**)&srcaudio->data[0], srcaudio->nb_samples);
|
|
if (err < 0)
|
|
throwFFMPEGError(err, "swr_convert()");
|
|
}
|
|
}
|
|
while (!gotFrame);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AudioConverter::feedEncoder()
|
|
{
|
|
int gotFrame = 0, err;
|
|
uint8_t *output;
|
|
int out_linesize;
|
|
int avail;
|
|
|
|
if (!m_resampler)
|
|
return false;
|
|
|
|
if (!m_encoderInitialized)
|
|
initEncoder();
|
|
|
|
assert(m_avpktOutUsed == m_avpktOut.size);
|
|
|
|
const size_t bytesPerSample = m_destinationFormat.mBitsPerChannel / 8;
|
|
const size_t bytesPerFrame = m_destinationFormat.mChannelsPerFrame * bytesPerSample;
|
|
const size_t requiredBytes = bytesPerFrame * ENCODER_FRAME_SAMPLES;
|
|
|
|
while (m_audioFramePrebuf.size() < requiredBytes && (avail = swr_get_out_samples(m_resampler, 0)) > 0)
|
|
{
|
|
av_samples_alloc(&output, &out_linesize, m_destinationFormat.mChannelsPerFrame,
|
|
avail, m_encoder->sample_fmt, 0);
|
|
|
|
if ((avail = swr_convert(m_resampler, &output, avail, nullptr, 0)) < 0)
|
|
{
|
|
av_freep(&output);
|
|
throwFFMPEGError(err, "swr_convert()");
|
|
}
|
|
|
|
#ifdef DEBUG_AUDIOCONVERTER
|
|
m_resamplerOutput.write((char*) output, avail * bytesPerFrame);
|
|
m_resamplerOutput.flush();
|
|
#endif
|
|
|
|
m_audioFramePrebuf.push(output, avail * bytesPerFrame);
|
|
av_freep(&output);
|
|
}
|
|
|
|
av_init_packet(&m_avpktOut);
|
|
m_avpktOut.data = 0;
|
|
m_avpktOut.size = 0;
|
|
m_avpktOutUsed = 0;
|
|
|
|
if (m_audioFramePrebuf.size() >= requiredBytes)
|
|
{
|
|
try
|
|
{
|
|
err = avcodec_fill_audio_frame(m_audioFrame, m_destinationFormat.mChannelsPerFrame,
|
|
m_targetFormat, m_audioFramePrebuf.data(), requiredBytes, 0);
|
|
|
|
if (err < 0)
|
|
throwFFMPEGError(err, "avcodec_fill_audio_frame()");
|
|
|
|
#if 0
|
|
err = avcodec_encode_audio2(m_encoder, &m_avpktOut, m_audioFrame, &gotFrame);
|
|
if (err < 0)
|
|
throwFFMPEGError(err, "avcodec_encode_audio2()");
|
|
#else
|
|
#warning TODO: test this new avcodec encoder code
|
|
err = avcodec_send_frame(m_encoder, m_audioFrame);
|
|
|
|
if (err < 0) {
|
|
if (err == AVERROR(EAGAIN)) {
|
|
// we need to consume more packets before sending
|
|
// TODO: handle this case properly.
|
|
// for now, we just proceed to throw an error to avoid dropping a frame
|
|
}
|
|
if (err < 0) {
|
|
throwFFMPEGError(err, "avcodec_send_frame()");
|
|
}
|
|
}
|
|
|
|
err = avcodec_receive_packet(m_encoder, &m_avpkt);
|
|
|
|
if (err < 0) {
|
|
gotFrame = false;
|
|
if (err == AVERROR(EAGAIN)) {
|
|
// we need to send more frames before consuming a packet
|
|
err = 0;
|
|
}
|
|
if (err < 0) {
|
|
throwFFMPEGError(err, "avcodec_receive_packet()");
|
|
}
|
|
} else {
|
|
gotFrame = true;
|
|
}
|
|
#endif
|
|
|
|
m_audioFramePrebuf.consume(requiredBytes);
|
|
|
|
#ifdef DEBUG_AUDIOCONVERTER
|
|
if (gotFrame)
|
|
m_encoderOutput.write((char*) m_avpktOut.data, m_avpktOut.size);
|
|
#endif
|
|
}
|
|
catch (...)
|
|
{
|
|
m_audioFramePrebuf.consume(requiredBytes);
|
|
throw;
|
|
}
|
|
|
|
return gotFrame;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
uint32_t AudioConverter::CAChannelCountToLayout(UInt32 numChannels)
|
|
{
|
|
// TODO: this is just wild guessing
|
|
switch (numChannels)
|
|
{
|
|
case 1:
|
|
return AV_CH_LAYOUT_MONO;
|
|
case 2:
|
|
return AV_CH_LAYOUT_STEREO;
|
|
case 3:
|
|
return AV_CH_LAYOUT_SURROUND;
|
|
case 4:
|
|
return AV_CH_LAYOUT_4POINT0;
|
|
case 5:
|
|
return AV_CH_LAYOUT_4POINT1;
|
|
case 6:
|
|
return AV_CH_LAYOUT_5POINT1;
|
|
case 7:
|
|
return AV_CH_LAYOUT_6POINT1;
|
|
case 8:
|
|
return AV_CH_LAYOUT_7POINT1;
|
|
default:
|
|
return AV_CH_LAYOUT_STEREO;
|
|
}
|
|
}
|
|
|
|
enum AVSampleFormat AudioConverter::CACodecSampleFormat(const AudioStreamBasicDescription* desc)
|
|
{
|
|
if (desc->mFormatFlags & kAudioFormatFlagIsFloat)
|
|
{
|
|
if (desc->mBitsPerChannel == 32)
|
|
return AV_SAMPLE_FMT_FLT;
|
|
else if (desc->mBitsPerChannel == 64)
|
|
return AV_SAMPLE_FMT_DBL;
|
|
else
|
|
return AV_SAMPLE_FMT_NONE;
|
|
}
|
|
else
|
|
{
|
|
|
|
switch (desc->mBitsPerChannel)
|
|
{
|
|
case 8: return AV_SAMPLE_FMT_U8;
|
|
case 16: return AV_SAMPLE_FMT_S16;
|
|
case 24: return AV_SAMPLE_FMT_S32; // FIXME: 24-bits?
|
|
case 32: return AV_SAMPLE_FMT_S32;
|
|
default: return AV_SAMPLE_FMT_NONE;
|
|
}
|
|
}
|
|
}
|
|
|
|
enum AVCodecID AudioConverter::CACodecToAV(const AudioStreamBasicDescription* desc)
|
|
{
|
|
switch (desc->mFormatID)
|
|
{
|
|
case kAudioFormatLinearPCM:
|
|
{
|
|
if (desc->mFormatFlags & kAudioFormatFlagIsFloat)
|
|
{
|
|
if (desc->mFormatFlags & kAudioFormatFlagIsBigEndian)
|
|
{
|
|
if (desc->mBitsPerChannel == 32)
|
|
return AV_CODEC_ID_PCM_F32BE;
|
|
else if (desc->mBitsPerChannel == 64)
|
|
return AV_CODEC_ID_PCM_F64BE;
|
|
}
|
|
else
|
|
{
|
|
if (desc->mBitsPerChannel == 32)
|
|
return AV_CODEC_ID_PCM_F32LE;
|
|
else if (desc->mBitsPerChannel == 64)
|
|
return AV_CODEC_ID_PCM_F64LE;
|
|
}
|
|
}
|
|
else if (desc->mFormatFlags & kAudioFormatFlagIsSignedInteger)
|
|
{
|
|
enum AVCodecID cid;
|
|
|
|
switch (desc->mBitsPerChannel)
|
|
{
|
|
case 8: cid = AV_CODEC_ID_PCM_S8; break;
|
|
case 16: cid = AV_CODEC_ID_PCM_S16LE; break;
|
|
case 24: cid = AV_CODEC_ID_PCM_S24LE; break;
|
|
case 32: cid = AV_CODEC_ID_PCM_S32LE; break;
|
|
default: return AV_CODEC_ID_NONE;
|
|
}
|
|
|
|
if (desc->mBitsPerChannel != 8 && desc->mFormatFlags & kAudioFormatFlagIsBigEndian)
|
|
cid = (enum AVCodecID)(int(cid)+1);
|
|
return cid;
|
|
}
|
|
else
|
|
{
|
|
enum AVCodecID cid;
|
|
|
|
switch (desc->mBitsPerChannel)
|
|
{
|
|
case 8: cid = AV_CODEC_ID_PCM_U8; break;
|
|
case 16: cid = AV_CODEC_ID_PCM_U16LE; break;
|
|
case 24: cid = AV_CODEC_ID_PCM_U24LE; break;
|
|
case 32: cid = AV_CODEC_ID_PCM_U32LE; break;
|
|
default: return AV_CODEC_ID_NONE;
|
|
}
|
|
|
|
if (desc->mBitsPerChannel != 8 && desc->mFormatFlags & kAudioFormatFlagIsBigEndian)
|
|
cid = (enum AVCodecID)(int(cid)+1);
|
|
return cid;
|
|
}
|
|
return AV_CODEC_ID_NONE;
|
|
}
|
|
case kAudioFormatULaw:
|
|
return AV_CODEC_ID_PCM_MULAW;
|
|
case kAudioFormatALaw:
|
|
return AV_CODEC_ID_PCM_ALAW;
|
|
case kAudioFormatMPEGLayer1:
|
|
return AV_CODEC_ID_MP1;
|
|
case kAudioFormatMPEGLayer2:
|
|
return AV_CODEC_ID_MP2;
|
|
case kAudioFormatMPEGLayer3:
|
|
return AV_CODEC_ID_MP3;
|
|
case kAudioFormatAC3:
|
|
case kAudioFormat60958AC3: // TODO: is this correct?
|
|
return AV_CODEC_ID_AC3;
|
|
case kAudioFormatAppleIMA4:
|
|
return AV_CODEC_ID_ADPCM_IMA_DK4; // TODO: is this correct?
|
|
case kAudioFormatMPEG4AAC:
|
|
case kAudioFormatMPEG4AAC_HE:
|
|
case kAudioFormatMPEG4AAC_LD:
|
|
case kAudioFormatMPEG4AAC_ELD:
|
|
case kAudioFormatMPEG4AAC_ELD_SBR:
|
|
case kAudioFormatMPEG4AAC_ELD_V2:
|
|
case kAudioFormatMPEG4AAC_HE_V2:
|
|
case kAudioFormatMPEG4AAC_Spatial:
|
|
return AV_CODEC_ID_AAC;
|
|
case kAudioFormatMPEG4CELP:
|
|
return AV_CODEC_ID_QCELP; // TODO: is this correct?
|
|
case kAudioFormatAMR:
|
|
return AV_CODEC_ID_AMR_NB;
|
|
case kAudioFormatiLBC:
|
|
return AV_CODEC_ID_ILBC;
|
|
case kAudioFormatAppleLossless:
|
|
return AV_CODEC_ID_APE;
|
|
case kAudioFormatMicrosoftGSM:
|
|
return AV_CODEC_ID_GSM_MS;
|
|
case kAudioFormatMACE3:
|
|
return AV_CODEC_ID_MACE3;
|
|
case kAudioFormatMACE6:
|
|
return AV_CODEC_ID_MACE6;
|
|
default:
|
|
return AV_CODEC_ID_NONE;
|
|
}
|
|
}
|
|
|