Files
dl_old/dl/system/dl_audio_loader.cpp
2026-07-04 02:23:47 +08:00

583 lines
15 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "dl_audio_loader.h"
#ifndef DL_DISABLE_AUDIO
#include <fstream>
#include <vorbis/codec.h>
#include <vorbis/vorbisfile.h>
#include <vorbis/vorbisenc.h>
#include <FLAC++/decoder.h>
#define MINIMP3_IMPLEMENTATION
#include <minimp3_ex.h>
#include "system/dl_platform.h"
#include "io/dl_file.h"
#include "memory/dl_stream_output.h"
namespace dl
{
#ifndef DL_ENDIAN_LITTLE
#define fourccRIFF 'RIFF'
#define fourccDATA 'data'
#define fourccFMT 'fmt '
#define fourccWAVE 'WAVE'
#define fourccXWMA 'XWMA'
#define fourccDPDS 'dpds'
#else
#define fourccRIFF 'FFIR'
#define fourccDATA 'atad'
#define fourccFMT ' tmf'
#define fourccWAVE 'EVAW'
#define fourccXWMA 'AMWX'
#define fourccDPDS 'sdpd'
#endif
bool FindChunk(std::ifstream& ifs, uint32_t fourcc, uint32_t& size, uint32_t& pos)
{
bool ret = true;
ifs.seekg(0);
if (ifs.fail())
return false;
uint32_t dwChunkType;
uint32_t dwChunkDataSize;
uint32_t dwRIFFDataSize = 0;
uint32_t dwFileType;
uint32_t bytesRead = 0;
uint32_t dwOffset = 0;
while (ret)
{
ifs.read((char*)&dwChunkType, sizeof(uint32_t));
ifs.read((char*)&dwChunkDataSize, sizeof(uint32_t));
switch (dwChunkType)
{
case fourccRIFF:
dwRIFFDataSize = dwChunkDataSize;
dwChunkDataSize = 4;
ifs.read((char*)&dwFileType, sizeof(uint32_t));
break;
default:
ifs.seekg(dwChunkDataSize, std::ios::cur);
if (ifs.fail())
return false;
break;
}
dwOffset += sizeof(uint32_t) * 2;
if (dwChunkType == fourcc)
{
size = dwChunkDataSize;
pos = dwOffset;
return true;
}
dwOffset += dwChunkDataSize;
if (bytesRead >= dwRIFFDataSize)
return false;
}
return true;
}
bool ReadChunkData(std::ifstream& ifs, void* buffer, uint32_t size, uint32_t pos)
{
ifs.seekg(pos);
if (ifs.fail())
return false;
ifs.read((char*)buffer, size);
return true;
}
bool AudioLoader::ReadWav(std::string_view path_name, PCM& pcm)
{
std::ifstream ifs = File::Get(path_name);
if (!ifs.is_open())
{
log_err("打开文件失败:{}", path_name);
return false;
}
uint32_t dwChunkSize;
uint32_t dwChunkPosition;
//查找riff块
FindChunk(ifs, fourccRIFF, dwChunkSize, dwChunkPosition);
uint32_t filetype;
ReadChunkData(ifs, &filetype, sizeof(uint32_t), dwChunkPosition);
if (filetype != fourccWAVE)
{
log_err("匹配标记失败(fourccWAVE):{}", path_name);
return false;
}
//查找fmt块
if (!FindChunk(ifs, fourccFMT, dwChunkSize, dwChunkPosition))
{
log_err("查找块失败(fourccFMT):{}", path_name);
return false;
}
//读wave信息
if (dwChunkSize != sizeof(WAVEFormat))
{
log_err("读取块失败(wave_format):{},size {}", path_name, dwChunkSize);
return false;
};
WAVEFormat wave_format;
if (!ReadChunkData(ifs, &wave_format, dwChunkSize, dwChunkPosition))
{
log_err("读取块失败(wave_format):{}", path_name);
return false;
};
//查找音频数据
if (!FindChunk(ifs, fourccDATA, dwChunkSize, dwChunkPosition))
{
log_err("查找块失败(fourccDATA):{}", path_name);
return false;
};
pcm._buf.resize(dwChunkSize);
if (!ReadChunkData(ifs, pcm._buf.data(), dwChunkSize, dwChunkPosition))
{
log_err("读取块失败(pcm数据):{}", path_name);
return false;
};
pcm._numChannel = wave_format.numChannels;
pcm._bitPerSample = wave_format.bitsPerSample;
pcm._freq = wave_format.sampleRate;
if (pcm._bitPerSample == 32)
{
log_err("不支持32位:{}", path_name);
return false;
}
return true;
}
bool AudioLoader::ReadOgg(std::string_view path_name, PCM& pcm)
{
FILE* file = File::GetFile(path_name);
if (!file)
{
log_err("打开文件失败:{}", path_name);
return false;
}
OggVorbis_File vf;
int eof = 0;
int current_section;
int ov_ret = ov_open_callbacks(file, &vf, NULL, 0, OV_CALLBACKS_NOCLOSE);
if (ov_ret < 0)
{
log_err("不是ogg文件:{}", path_name );
fclose(file);
return false;
}
// 写入pcm信息
{
vorbis_info* vi = ov_info(&vf, -1);
pcm._numChannel = vi->channels;
pcm._freq = vi->rate;
pcm._bitPerSample = 16;
}
// 写入buffer
std::byte pcmout[4096];
Buffer& buf = pcm._buf;
while (!eof)
{
long ret = ov_read(&vf, (char*)pcmout, sizeof(pcmout), 0, 2, 1, &current_section);
if (ret == 0) {
/* EOF */
eof = 1;
}
else if (ret < 0) {
/* error in the stream. Not a problem, just reporting it in
case we (the app) cares. In this case, we don't. */
}
else {
/* we don't bother dealing with sample rate changes, etc, but
you'll have to*/
buf.append(pcmout, ret);
}
}
ov_clear(&vf);
fclose(file);
return true;
}
FLAC__StreamDecoderWriteStatus write_callback(const FLAC__StreamDecoder* decoder,
const FLAC__Frame* frame, const FLAC__int32* const buffer[], void* client_data)
{
AudioLoader::PCM* pcm = (AudioLoader::PCM*)client_data;
Buffer& buf = pcm->_buf;
if (pcm->_buf.empty())
{
FLAC__uint32 total_size = (FLAC__uint32)(
pcm->_freq * pcm->_numChannel * (pcm->_bitPerSample / 8));
buf.reserve(total_size);
}
if (pcm->_freq == 0)
{
log_err("采样率为0!");
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
}
if (pcm->_numChannel != 2 || pcm->_bitPerSample != 16)
{
log_err("通道数不为2,或采样数不为16!");
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
}
if (frame->header.channels != 2)
{
log_err("frame通道数不为2!");
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
}
if (buffer[0] == NULL)
{
log_err("buffer[0]为空!");
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
}
if (buffer[1] == NULL)
{
log_err("buffer[1]为空!");
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
}
for (size_t i = 0; i < frame->header.blocksize; ++i)
{
std::byte* p0 = (std::byte*)(buffer[0] + i);
buf.append(p0, 2);
std::byte* p1 = (std::byte*)(buffer[1] + i);
buf.append(p1, 2);
}
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
void metadata_callback(const FLAC__StreamDecoder* decoder, const FLAC__StreamMetadata* metadata, void* client_data)
{
AudioLoader::PCM* pcm = (AudioLoader::PCM*)client_data;
if (metadata->type != FLAC__METADATA_TYPE_STREAMINFO)
{
log_err("获取metadata类型失败");
return;
}
pcm->_bitPerSample = metadata->data.stream_info.bits_per_sample;
pcm->_numChannel = metadata->data.stream_info.channels;
pcm->_freq = metadata->data.stream_info.sample_rate;
}
void error_callback(const FLAC__StreamDecoder* decoder, FLAC__StreamDecoderErrorStatus status, void* client_data)
{
log_warn("出现错误:{}", FLAC__StreamDecoderErrorStatusString[status]);
}
bool AudioLoader::ReadFlac(std::string_view path_name, PCM& pcm)
{
FLAC__StreamDecoder* decoder = FLAC__stream_decoder_new();
if (!decoder)
{
log_err("创建decoder失败!");
return false;
}
(void)FLAC__stream_decoder_set_md5_checking(decoder, true);
FILE* fp = File::GetFile(path_name);
FLAC__StreamDecoderInitStatus init_status = FLAC__stream_decoder_init_FILE(
decoder, fp,
write_callback, metadata_callback, error_callback, &pcm);
if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK)
{
log_err("初始化decoder失败: %s\n", FLAC__StreamDecoderInitStatusString[init_status]);
FLAC__stream_decoder_delete(decoder);
//fclose(fp);
return false;
}
FLAC__bool ok = FLAC__stream_decoder_process_until_end_of_stream(decoder);
if (!ok)
{
log_msg("解码失败!{}",
FLAC__StreamDecoderStateString[FLAC__stream_decoder_get_state(decoder)]);
FLAC__stream_decoder_delete(decoder);
//fclose(fp);
return false;
}
FLAC__stream_decoder_delete(decoder);
//fclose(fp); clang提示是重复调用
return true;
}
mp3dec_t* g_mp3dec = nullptr;
bool AudioLoader::ReadMp3(std::string_view path_name, PCM& pcm)
{
Buffer buf = File::GetBuffer(path_name);
if (buf.empty())
return false;
if (!g_mp3dec)
{
g_mp3dec = new mp3dec_t;
mp3dec_init(g_mp3dec);
}
mp3dec_file_info_t info;
mp3dec_load_buf(g_mp3dec, (uint8_t*)buf.data(), buf.size(), &info, nullptr, nullptr);
pcm._buf = CreateBuffer(CreateBufferView(
(uint8_t*)info.buffer,
info.samples * sizeof(mp3d_sample_t)));
pcm._bitPerSample = 16;
pcm._freq = info.hz;
pcm._numChannel = info.channels;
free(info.buffer);
return true;
}
#if DL_PLATFORM == DL_PLATFORM_WIN32
bool AudioLoader::WriteOgg(std::string_view path_name, const PCM& pcm)
{
std::ofstream ofs = File::Set(path_name);
if (!ofs.is_open())
{
log_err("文件创建失败:{}", path_name);
return false;
}
#define READ 1024
//static char readbuffer[READ * 4 + 44]; /* out of the data segment, not the stack */
ogg_stream_state os; /* take physical pages, weld into a logical
stream of packets */
ogg_page og; /* one Ogg bitstream page. Vorbis packets are inside */
ogg_packet op; /* one raw packet of data for decode */
vorbis_info vi; /* struct that stores all the static vorbis bitstream
settings */
vorbis_comment vc; /* struct that stores all the user comments */
vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */
vorbis_block vb; /* local working space for packet->PCM decode */
int eos = 0, ret;
/********** Encode setup ************/
vorbis_info_init(&vi);
/* choose an encoding mode. A few possibilities commented out, one
actually used: */
/*********************************************************************
Encoding using a VBR quality mode. The usable range is -.1
(lowest quality, smallest file) to 1. (highest quality, largest file).
Example quality mode .4: 44kHz stereo coupled, roughly 128kbps VBR
ret = vorbis_encode_init_vbr(&vi,2,44100,.4);
---------------------------------------------------------------------
Encoding using an average bitrate mode (ABR).
example: 44kHz stereo coupled, average 128kbps VBR
ret = vorbis_encode_init(&vi,2,44100,-1,128000,-1);
---------------------------------------------------------------------
Encode using a quality mode, but select that quality mode by asking for
an approximate bitrate. This is not ABR, it is true VBR, but selected
using the bitrate interface, and then turning bitrate management off:
ret = ( vorbis_encode_setup_managed(&vi,2,44100,-1,128000,-1) ||
vorbis_encode_ctl(&vi,OV_ECTL_RATEMANAGE2_SET,NULL) ||
vorbis_encode_setup_init(&vi));
*********************************************************************/
ret = vorbis_encode_init_vbr(&vi, pcm._numChannel, (long)pcm._freq, 0.1f);
/* do not continue if setup failed; this can happen if we ask for a
mode that libVorbis does not support (eg, too low a bitrate, etc,
will return 'OV_EIMPL') */
if (ret)
{
log_err("vorbis_encode_init_vbr失败:", path_name);
return false;
}
/* add a comment */
vorbis_comment_init(&vc);
vorbis_comment_add_tag(&vc, "ENCODER", "dl_audio_loader.cpp");
/* set up the analysis state and auxiliary encoding storage */
vorbis_analysis_init(&vd, &vi);
vorbis_block_init(&vd, &vb);
/* set up our packet->stream encoder */
/* pick a random serial number; that way we can more likely build
chained streams just by concatenation */
//srand(time(NULL));
ogg_stream_init(&os, 0);//rand());
/* Vorbis streams begin with three headers; the initial header (with
most of the codec setup parameters) which is mandated by the Ogg
bitstream spec. The second header holds any comment fields. The
third header holds the bitstream codebook. We merely need to
make the headers, then pass them to libvorbis one at a time;
libvorbis handles the additional Ogg bitstream constraints */
{
ogg_packet header;
ogg_packet header_comm;
ogg_packet header_code;
vorbis_analysis_headerout(&vd, &vc, &header, &header_comm, &header_code);
ogg_stream_packetin(&os, &header); /* automatically placed in its own
page */
ogg_stream_packetin(&os, &header_comm);
ogg_stream_packetin(&os, &header_code);
/* This ensures the actual
* audio data will start on a new page, as per spec
*/
while (!eos) {
int result = ogg_stream_flush(&os, &og);
if (result == 0)break;
/*fwrite(og.header, 1, og.header_len, stdout);
fwrite(og.body, 1, og.body_len, stdout);*/
ofs.write((char*)og.header, og.header_len);
ofs.write((char*)og.body, og.body_len);
}
}
//readbuffer[0] = 0;
long buf_cur = 0;
long step = pcm._numChannel * 2;
while (!eos) {
long i;
long bytes = 0;
if (buf_cur + READ * step < (long)pcm._buf.size())
bytes = READ * step;
else
bytes = (long)pcm._buf.size() - buf_cur;
char* readbuffer = (char*)pcm._buf.data() + buf_cur;
//memcpy(readbuffer, pcm._buf.data() + buf_cur, READ * 4);
buf_cur += bytes;
//long bytes = fread(readbuffer, 1, READ * 4, stdin); /* stereo hardwired here */
if (bytes == 0) {
/* end of file. this can be done implicitly in the mainline,
but it's easier to see here in non-clever fashion.
Tell the library we're at end of stream so that it can handle
the last frame and mark end of stream in the output properly */
vorbis_analysis_wrote(&vd, 0);
}
else {
/* data to encode */
/* expose the buffer to submit data */
float** buffer = vorbis_analysis_buffer(&vd, READ);
/* uninterleave samples */
if (step == 4)
{
for (i = 0; i < bytes / 4; i++) {
buffer[0][i] = ((readbuffer[i * 4 + 1] << 8) |
(0x00ff & (int)readbuffer[i * 4])) / 32768.f;
buffer[1][i] = ((readbuffer[i * 4 + 3] << 8) |
(0x00ff & (int)readbuffer[i * 4 + 2])) / 32768.f;
}
}
else if(step == 2)
{
for (i = 0; i < bytes / 2; i++) {
buffer[0][i] = ((readbuffer[i * 2 + 1] << 8) | (0x00ff & (int)readbuffer[i * 2])) / 32768.f;
//buffer[1][i] = buffer[0][i];
}
}
/* tell the library how much we actually submitted */
vorbis_analysis_wrote(&vd, i);
}
/* vorbis does some data preanalysis, then divvies up blocks for
more involved (potentially parallel) processing. Get a single
block for encoding now */
while (vorbis_analysis_blockout(&vd, &vb) == 1) {
/* analysis, assume we want to use bitrate management */
vorbis_analysis(&vb, NULL);
vorbis_bitrate_addblock(&vb);
while (vorbis_bitrate_flushpacket(&vd, &op)) {
/* weld the packet into the bitstream */
ogg_stream_packetin(&os, &op);
/* write out pages (if any) */
while (!eos) {
int result = ogg_stream_pageout(&os, &og);
if (result == 0)
break;
/*fwrite(og.header, 1, og.header_len, stdout);
fwrite(og.body, 1, og.body_len, stdout);*/
ofs.write((char*)og.header, og.header_len);
ofs.write((char*)og.body, og.body_len);
/* this could be set above, but for illustrative purposes, I do
it here (to show that vorbis does know where the stream ends) */
if (ogg_page_eos(&og))eos = 1;
}
}
}
}
/* clean up and exit. vorbis_info_clear() must be called last */
ogg_stream_clear(&os);
vorbis_block_clear(&vb);
vorbis_dsp_clear(&vd);
vorbis_comment_clear(&vc);
vorbis_info_clear(&vi);
/* ogg_page and ogg_packet structs always point to storage in
libvorbis. They're never freed or manipulated directly */
return true;
}
#endif
AudioLoader g_audioLoader;
AudioLoader::~AudioLoader()
{
delete g_mp3dec;
}
}
#endif