1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 07:48:11 +00:00
serenity/Userland/Libraries/LibAudio/MP3Loader.h
Zaggy1024 49be09e5b2 LibAudio: Skip ID3 tags before synchronizing to MP3 frames
Previously, we would just start from byte 0 and check individual bytes
of the file until we find two bytes starting with `FF F`, and then
assume that that was the MP3 frame sync code. However, some ID3v2 tags
do not have to be what is referred to as "unsynchronized", meaning that
they can contain that `FF F` sequence and cause our decoder to think it
has found a frame.

To avoid this happening, we can read a minimal amount of the ID3 header
to determine how many bytes to skip before attempting to find the MP3
frames.

This allows the recent podcast with Andreas to play here:

https://changelog.com/podcast/554
2023-08-26 18:43:23 -04:00

79 lines
3.3 KiB
C++

/*
* Copyright (c) 2021, Arne Elster <arne@elster.li>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include "Loader.h"
#include "MP3Types.h"
#include <AK/BitStream.h>
#include <AK/MemoryStream.h>
#include <AK/Tuple.h>
#include <LibDSP/MDCT.h>
namespace Audio {
namespace MP3::Tables {
struct ScaleFactorBand;
}
class MP3LoaderPlugin : public LoaderPlugin {
public:
explicit MP3LoaderPlugin(NonnullOwnPtr<SeekableStream> stream);
virtual ~MP3LoaderPlugin() = default;
static bool sniff(SeekableStream& stream);
static ErrorOr<NonnullOwnPtr<LoaderPlugin>, LoaderError> create(NonnullOwnPtr<SeekableStream>);
virtual ErrorOr<Vector<FixedArray<Sample>>, LoaderError> load_chunks(size_t samples_to_read_from_input) override;
virtual MaybeLoaderError reset() override;
virtual MaybeLoaderError seek(int const position) override;
virtual int loaded_samples() override { return m_loaded_samples; }
virtual int total_samples() override { return m_total_samples; }
virtual u32 sample_rate() override { return m_sample_rate; }
virtual u16 num_channels() override { return m_num_channels; }
virtual PcmSampleFormat pcm_format() override { return m_sample_format; }
virtual DeprecatedString format_name() override { return "MP3 (.mp3)"; }
private:
MaybeLoaderError initialize();
static MaybeLoaderError skip_id3(SeekableStream& stream);
static MaybeLoaderError synchronize(BigEndianInputBitStream& stream, size_t sample_index);
MaybeLoaderError synchronize();
MaybeLoaderError build_seek_table();
ErrorOr<MP3::Header, LoaderError> read_header();
ErrorOr<MP3::MP3Frame, LoaderError> read_next_frame();
ErrorOr<MP3::MP3Frame, LoaderError> read_frame_data(MP3::Header const&);
MaybeLoaderError read_side_information(MP3::MP3Frame&);
ErrorOr<size_t, LoaderError> read_scale_factors(MP3::MP3Frame&, BigEndianInputBitStream& reservoir, size_t granule_index, size_t channel_index);
MaybeLoaderError read_huffman_data(MP3::MP3Frame&, BigEndianInputBitStream& reservoir, size_t granule_index, size_t channel_index, size_t granule_bits_read);
static AK::Array<float, 576> calculate_frame_exponents(MP3::MP3Frame const&, size_t granule_index, size_t channel_index);
static void reorder_samples(MP3::Granule&, u32 sample_rate);
static void reduce_alias(MP3::Granule&, size_t max_subband_index = 576);
static void process_stereo(MP3::MP3Frame&, size_t granule_index);
static void transform_samples_to_time(Array<float, 576> const& input, size_t input_offset, Array<float, 36>& output, MP3::BlockType block_type);
static void synthesis(Array<float, 1024>& V, Array<float, 32>& samples, Array<float, 32>& result);
static ReadonlySpan<MP3::Tables::ScaleFactorBand> get_scalefactor_bands(MP3::Granule const&, int samplerate);
SeekTable m_seek_table;
AK::Array<AK::Array<AK::Array<float, 18>, 32>, 2> m_last_values {};
AK::Array<AK::Array<float, 1024>, 2> m_synthesis_buffer {};
static DSP::MDCT<36> s_mdct_36;
static DSP::MDCT<12> s_mdct_12;
u32 m_sample_rate { 0 };
u8 m_num_channels { 0 };
PcmSampleFormat m_sample_format { PcmSampleFormat::Int16 };
int m_total_samples { 0 };
size_t m_loaded_samples { 0 };
AK::Optional<MP3::MP3Frame> m_current_frame;
OwnPtr<BigEndianInputBitStream> m_bitstream;
AllocatingMemoryStream m_bit_reservoir;
};
}