583 lines
15 KiB
C++
583 lines
15 KiB
C++
#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, ¤t_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 |