Files
MuditaOS/module-audio/Audio/decoder/decoderMP3.cpp
Marcin Smoczyński 871b250d86 [EGD-4534] Change audio data path synchronization
Refactor audio data path to fix several synchronization issues and
excessive copy operations on large memory blocks. Introduce
audio::Stream data structure to allow connecting audio source and sink
with a zero-copy capability.

Introduce system mechanisms:
 - critical section guard lock needed for stream synchronization
 - non-cacheable memory allocator to allocate memory for DMA safe
   buffers

Update the Googletest CMake template to match the capabilities of the
Catch2 template.

Signed-off-by: Marcin Smoczyński <smoczynski.marcin@gmail.com>
Signed-off-by: Hubert Chrzaniuk <hubert.chrzaniuk@mudita.com>
2020-12-17 12:20:40 +01:00

281 lines
8.8 KiB
C++

// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#define MINIMP3_ONLY_MP3
#define MINIMP3_NO_SIMD
#define MINIMP3_IMPLEMENTATION
#include "decoderMP3.hpp"
#include <array>
#include <cstdio>
namespace audio
{
decoderMP3::decoderMP3(const char *fileName) : Decoder(fileName)
{
if (fileSize == 0) {
return;
}
mp3d = std::make_unique<mp3dec_t>();
mp3dec_init(mp3d.get());
if (!find_first_valid_frame()) {
return;
}
isInitialized = true;
}
void decoderMP3::setPosition(float pos)
{
decoderNotFirstRun = false;
std::fseek(fd, (pos * fileSize) + firstValidFrameFileOffset, SEEK_SET);
// TODO: M.P Currently calculating MP3 position is unsupported, in general seeking is supported though.
// position += (float) ((float) (samplesToReadChann / chanNumber) / (float) sampleRate);
}
void decoderMP3::fetchTagsSpecific()
{
std::fseek(fd, firstValidFrameFileOffset + 4, SEEK_SET);
auto buff = std::make_unique<uint8_t[]>(firstValidFrameByteSize);
std::fread(buff.get(), 1, firstValidFrameByteSize, fd);
xing_info_t xinfo = {};
if (parseXingHeader(buff.get(), firstValidFrameByteSize, &xinfo)) {
// valid xing header found
tag->total_duration_s = xinfo.TotalFrames * (samplesPerFrame) / sampleRate;
}
else {
// Scan through whole file and count frames
uint32_t frames_count = get_frames_count();
tag->total_duration_s = frames_count * (samplesPerFrame) / sampleRate;
}
std::rewind(fd);
}
bool decoderMP3::find_first_valid_frame()
{
mp3dec_frame_info_t info = {};
int32_t bytesAvailable = DECODER_BUFFER_SIZE;
uint32_t bufferIndex = 0;
auto decBuffer = std::make_unique<uint8_t[]>(DECODER_BUFFER_SIZE);
std::rewind(fd);
if (std::fread(decBuffer.get(), 1, DECODER_BUFFER_SIZE, fd) == 0) {
return false;
}
for (;;) {
// refill buffer if necessary(only if over 87,5% of bytes are consumed)
if (bufferIndex > (DECODER_BUFFER_SIZE - (DECODER_BUFFER_SIZE / 8))) {
memcpy(&decBuffer[0], &decBuffer[bufferIndex], bytesAvailable);
uint32_t bytesRead =
std::fread(&decBuffer[bytesAvailable], 1, DECODER_BUFFER_SIZE - bytesAvailable, fd);
if (bytesRead == 0) {
return false;
}
bytesAvailable += bytesRead;
bufferIndex = 0;
}
for (;;) {
uint32_t smpl =
mp3dec_decode_frame(mp3d.get(), &decBuffer[bufferIndex], bytesAvailable, nullptr, &info);
bufferIndex += info.frame_bytes;
bytesAvailable -= info.frame_bytes;
// Valid frame
if (smpl && info.frame_bytes) {
// Fill necessary parameters
samplesPerFrame = smpl;
sampleRate = info.hz;
chanNumber = info.channels;
firstValidFrameByteSize = (144 * info.bitrate_kbps * 1000 / info.hz);
firstValidFrameFileOffset = std::ftell(fd) - bytesAvailable - firstValidFrameByteSize;
std::rewind(fd);
return true;
}
// Decoder skipped ID3 or invalid data
else if ((smpl == 0) && info.frame_bytes) {
}
// insufficient data
else if ((info.frame_bytes == 0) && (smpl == 0)) {
break;
}
}
}
}
uint32_t decoderMP3::get_frames_count()
{
int32_t bytesAvailable = DECODER_BUFFER_SIZE;
uint32_t frames_count = 0;
mp3dec_frame_info_t info = {};
bool last_refill = false;
uint32_t bufferIndex = 0;
auto decBuffer = std::make_unique<uint8_t[]>(DECODER_BUFFER_SIZE);
// Jump to the file beginning
std::rewind(fd);
/* Fill decBuffer */
if (std::fread(decBuffer.get(), 1, DECODER_BUFFER_SIZE, fd) == 0) {
return 0;
}
refill:
// refill buffer if necessary(only if over 87,5% of bytes are consumed)
if (bufferIndex > (DECODER_BUFFER_SIZE - (DECODER_BUFFER_SIZE / 8))) {
memcpy(&decBuffer[0], &decBuffer[bufferIndex], bytesAvailable);
uint32_t bytesRead =
std::fread(decBuffer.get() + bytesAvailable, 1, DECODER_BUFFER_SIZE - bytesAvailable, fd);
if (bytesRead != (DECODER_BUFFER_SIZE - bytesAvailable)) {
last_refill = true;
}
bytesAvailable += bytesRead;
bufferIndex = 0;
}
while (1) {
uint32_t smpl = mp3dec_decode_frame(mp3d.get(), &decBuffer[bufferIndex], bytesAvailable, nullptr, &info);
bufferIndex += info.frame_bytes;
bytesAvailable -= info.frame_bytes;
// Valid frame
if (smpl && info.frame_bytes) {
frames_count++;
}
// Decoder skipped ID3 or invalid data
if ((smpl == 0) && info.frame_bytes) {
frames_count++;
}
// Last frame
if (last_refill && (info.frame_bytes == 0) && (smpl == 0)) {
return frames_count;
}
// insufficient data
if ((info.frame_bytes == 0) && (smpl == 0)) {
goto refill;
}
}
}
uint32_t decoderMP3::decode(uint32_t samplesToRead, int16_t *pcmData)
{
mp3dec_frame_info_t info = {0, 0, 0, 0, 0, 0};
if (!decoderNotFirstRun) {
decoderBuffer = std::make_unique<uint8_t[]>(DECODER_BUFFER_SIZE);
pcmsamplesbuffer = std::make_unique<uint16_t[]>(pcmsamplesbuffer_size);
// Fill decoderBuffer
uint32_t bytesRead = std::fread(decoderBuffer.get(), 1, DECODER_BUFFER_SIZE, fd);
if (bytesRead == 0) {
return 0;
}
bytesAvailable = DECODER_BUFFER_SIZE;
decoderBufferIdx = 0;
decoderNotFirstRun = true;
}
else if (!lastRefill) {
bytesAvailable = DECODER_BUFFER_SIZE - decoderBufferIdx;
}
uint32_t samplesFetched = pcmsamplesbuffer_idx;
refill:
// refill buffer if necessary(only if over 87,5% of bytes consumed)
if (!lastRefill && (decoderBufferIdx > (DECODER_BUFFER_SIZE - (DECODER_BUFFER_SIZE / 8)))) {
memcpy(&decoderBuffer[0], &decoderBuffer[decoderBufferIdx], bytesAvailable);
uint32_t bytesRead =
std::fread(&decoderBuffer[bytesAvailable], 1, DECODER_BUFFER_SIZE - bytesAvailable, fd);
if (bytesRead != (DECODER_BUFFER_SIZE - bytesAvailable)) {
lastRefill = true;
}
bytesAvailable += bytesRead;
decoderBufferIdx = 0;
}
uint32_t samplesToReadChann = samplesToRead;
while (1) {
uint32_t smpl = 0;
if (samplesFetched < samplesToReadChann) {
smpl = mp3dec_decode_frame(mp3d.get(),
&decoderBuffer[decoderBufferIdx],
bytesAvailable,
(short *)&pcmsamplesbuffer[samplesFetched],
&info);
}
bytesAvailable -= info.frame_bytes;
decoderBufferIdx += info.frame_bytes;
// Valid frame
if (smpl && info.frame_bytes) {
samplesFetched += smpl * info.channels;
}
if (samplesFetched >= samplesToReadChann) {
break;
}
// Insufficient data
if (!lastRefill && (info.frame_bytes == 0) && (smpl == 0)) {
goto refill;
}
if (lastRefill && (info.frame_bytes == 0) && (smpl == 0)) {
return 0;
}
}
// Copy decoded samples to dest buffer
memcpy(pcmData, &pcmsamplesbuffer[0], samplesToReadChann * sizeof(int16_t));
// Manage samples in internal buffer
pcmsamplesbuffer_idx = samplesFetched - samplesToReadChann;
memmove(&pcmsamplesbuffer[0], &pcmsamplesbuffer[samplesToReadChann], pcmsamplesbuffer_idx * sizeof(int16_t));
/* Calculate frame duration in seconds */
position += (float)((float)(chanNumber == 2 ? samplesToRead / chanNumber : samplesToRead) / (float)sampleRate);
return samplesToRead;
}
} // namespace audio