mirror of
https://github.com/mudita/MuditaOS.git
synced 2026-01-25 14:29:22 -05:00
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>
281 lines
8.8 KiB
C++
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
|