SDL2 & SMPEG2 - 尝试读取 MP3 的清空声音缓冲区
SDL2 & SMPEG2 - Empty sound buffer trying to read a MP3
我正在尝试使用 SDL2 附带的 SMPEG2 库在缓冲区中加载 MP3。每个 SMPEG 函数调用 returns 都没有错误,但是当我完成时,声音缓冲区充满了零。
代码如下:
bool LoadMP3(char* filename)
{
bool success = false;
const Uint32 Mp3ChunkLen = 4096;
SMPEG* mp3;
SMPEG_Info infoMP3;
Uint8 * ChunkBuffer;
Uint32 MP3Length = 0;
// Allocate a chunk buffer
ChunkBuffer = (Uint8*)malloc(Mp3ChunkLen);
SDL_RWops *mp3File = SDL_RWFromFile(filename, "rb");
if (mp3File != NULL)
{
mp3 = SMPEG_new_rwops(mp3File, &infoMP3, 1, 0);
if(mp3 != NULL)
{
if(infoMP3.has_audio)
{
Uint32 readLen;
// Inform the MP3 of the output audio specifications
SMPEG_actualSpec(mp3, &asDeviceSpecs); // static SDL_AudioSpec asDeviceSpecs; containing valid values after a call to SDL_OpenAudioDevice
// Enable the audio and disable the video.
SMPEG_enableaudio(mp3, 1);
SMPEG_enablevideo(mp3, 0);
// Play the MP3 once to get the size of the needed finale buffer
SMPEG_play(mp3);
while ((readLen = SMPEG_playAudio(mp3, ChunkBuffer, Mp3ChunkLen)) > 0)
{
MP3Length += readLen;
}
SMPEG_stop(mp3);
if(MP3Length > 0)
{
// Reallocate the buffer with the new length (if needed)
if (MP3Length != Mp3ChunkLen)
{
ChunkBuffer = (Uint8*)realloc(ChunkBuffer, MP3Length);
}
// Replay the entire MP3 into the new ChunkBuffer.
SMPEG_rewind(mp3);
SMPEG_play(mp3);
bool readBackSuccess = (MP3Length == SMPEG_playAudio(mp3, ChunkBuffer, MP3Length));
SMPEG_stop(mp3);
if(readBackSuccess)
{
// !!! Here, ChunkBuffer contains only zeros !!!
success = true;
}
}
}
SMPEG_delete(mp3);
mp3 = NULL;
}
SDL_RWclose(mp3File);
mp3File = NULL;
}
free(ChunkBuffer);
return success;
}
该代码广泛基于 SDL_Mixer,由于其局限性,我无法将其用于我的项目。
我知道 Ogg Vorbis 是更好的文件格式选择,但我正在移植一个非常古老的项目,它完全适用于 MP3。
我确定音响系统已正确初始化,因为我可以很好地播放 WAV 文件。它以 44100 的频率、2 个通道、1024 个样本和 AUDIO_S16SYS 格式(据我从 SMPEG 源中了解到,后者是强制性的)进行初始化。
我已经根据比特率、MP3 中的数据量和 OpenAudioDevice 音频规格计算了预期的缓冲区大小,一切都是一致的。
我不明白为什么除了缓冲区数据之外的所有东西似乎都在工作。
更新 #1
仍在尝试找出问题所在,我认为对 MP3 的支持可能无法正常工作,所以我创建了以下函数:
SMPEG *mpeg;
SMPEG_Info info;
mpeg = SMPEG_new(filename,&info, 1);
SMPEG_play(mpeg);
do { SDL_Delay(50); } while(SMPEG_status(mpeg) == SMPEG_PLAYING);
SMPEG_delete(mpeg);
播放了 MP3。所以,解码实际上应该是有效的。但这不是我需要的;我真的需要声音缓冲区数据,所以我可以将它发送到我的调音台。
经过大量修改、研究和挖掘 SMPEG 源代码后,我意识到我必须将 1 作为 SDLAudio 参数传递给 SMPEG_new_rwops 函数。
smpeg.h 中的评论具有误导性:
The sdl_audio parameter indicates if SMPEG should initialize the SDL audio subsystem. If not, you will have to use the SMPEG_playaudio() function below to extract the decoded data.
由于音频子系统已经初始化并且我正在使用 SMPEG_playaudio() 函数,所以我没有理由认为我需要此参数为非零。在 SMPEG 中,此参数会在打开时触发音频解压缩,但即使我调用 SMPEG_enableaudio(mp3, 1);
,数据也永远不会重新解析。这可能是一个 bug/a 可疑的功能。
由于我自己释放了 SDL_RWops 对象,因此 freesrc 参数有另一个问题,该参数需要为 0。
供以后参考,一旦 ChunkBuffer 有了 MP3 数据,如果要通过已经打开的音频设备播放,则需要通过 SDL_BuildAudioCVT/SDL_ConvertAudio。
最终的工作代码是:
// bool ReadMP3ToBuffer(char* filename)
bool success = false;
const Uint32 Mp3ChunkLen = 4096;
SDL_AudioSpec mp3Specs;
SMPEG* mp3;
SMPEG_Info infoMP3;
Uint8 * ChunkBuffer;
Uint32 MP3Length = 0;
// Allocate a chunk buffer
ChunkBuffer = (Uint8*)malloc(Mp3ChunkLen);
memset(ChunkBuffer, 0, Mp3ChunkLen);
SDL_RWops *mp3File = SDL_RWFromFile(filename, "rb"); // filename is a char* passed to the function.
if (mp3File != NULL)
{
mp3 = SMPEG_new_rwops(mp3File, &infoMP3, 0, 1);
if(mp3 != NULL)
{
if(infoMP3.has_audio)
{
Uint32 readLen;
// Get the MP3 audio specs for later conversion
SMPEG_wantedSpec(mp3, &mp3Specs);
SMPEG_enablevideo(mp3, 0);
// Play the MP3 once to get the size of the needed buffer in relation with the audio specs
SMPEG_play(mp3);
while ((readLen = SMPEG_playAudio(mp3, ChunkBuffer, Mp3ChunkLen)) > 0)
{
MP3Length += readLen;
}
SMPEG_stop(mp3);
if(MP3Length > 0)
{
// Reallocate the buffer with the new length (if needed)
if (MP3Length != Mp3ChunkLen)
{
ChunkBuffer = (Uint8*)realloc(ChunkBuffer, MP3Length);
memset(ChunkBuffer, 0, MP3Length);
}
// Replay the entire MP3 into the new ChunkBuffer.
SMPEG_rewind(mp3);
SMPEG_play(mp3);
bool readBackSuccess = (MP3Length == SMPEG_playAudio(mp3, ChunkBuffer, MP3Length));
SMPEG_stop(mp3);
if(readBackSuccess)
{
SDL_AudioCVT convertedSound;
// NOTE : static SDL_AudioSpec asDeviceSpecs; containing valid values after a call to SDL_OpenAudioDevice
if(SDL_BuildAudioCVT(&convertedSound, mp3Specs.format, mp3Specs.channels, mp3Specs.freq, asDeviceSpecs.format, asDeviceSpecs.channels, asDeviceSpecs.freq) >= 0)
{
Uint32 newBufferLen = MP3Length*convertedSound.len_mult;
// Make sure the audio length is a multiple of a sample size to avoid sound clicking
int sampleSize = ((asDeviceSpecs.format & 0xFF)/8)*asDeviceSpecs.channels;
newBufferLen &= ~(sampleSize-1);
// Allocate the new buffer and proceed with the actual conversion.
convertedSound.buf = (Uint8*)malloc(newBufferLen);
memcpy(convertedSound.buf, ChunkBuffer, MP3Length);
convertedSound.len = MP3Length;
if(SDL_ConvertAudio(&convertedSound) == 0)
{
// Save convertedSound.buf and convertedSound.len_cvt for future use in your mixer code.
// Dont forget to free convertedSound.buf once it's not used anymore.
success = true;
}
}
}
}
}
SMPEG_delete(mp3);
mp3 = NULL;
}
SDL_RWclose(mp3File);
mp3File = NULL;
}
free(ChunkBuffer);
return success;
注意 :当我用这段代码对它们重新采样时,我尝试过的一些 MP3 文件丢失了几毫秒并且在播放过程中过早中断。其他一些则没有。我可以在 Audacity 中重现相同的行为,所以我不确定发生了什么。我的代码可能仍然存在错误,SMPEG 中的错误,或者可能是 MP3 格式本身的已知问题。如果有人可以在评论中提供和解释,那就太好了!
我正在尝试使用 SDL2 附带的 SMPEG2 库在缓冲区中加载 MP3。每个 SMPEG 函数调用 returns 都没有错误,但是当我完成时,声音缓冲区充满了零。
代码如下:
bool LoadMP3(char* filename)
{
bool success = false;
const Uint32 Mp3ChunkLen = 4096;
SMPEG* mp3;
SMPEG_Info infoMP3;
Uint8 * ChunkBuffer;
Uint32 MP3Length = 0;
// Allocate a chunk buffer
ChunkBuffer = (Uint8*)malloc(Mp3ChunkLen);
SDL_RWops *mp3File = SDL_RWFromFile(filename, "rb");
if (mp3File != NULL)
{
mp3 = SMPEG_new_rwops(mp3File, &infoMP3, 1, 0);
if(mp3 != NULL)
{
if(infoMP3.has_audio)
{
Uint32 readLen;
// Inform the MP3 of the output audio specifications
SMPEG_actualSpec(mp3, &asDeviceSpecs); // static SDL_AudioSpec asDeviceSpecs; containing valid values after a call to SDL_OpenAudioDevice
// Enable the audio and disable the video.
SMPEG_enableaudio(mp3, 1);
SMPEG_enablevideo(mp3, 0);
// Play the MP3 once to get the size of the needed finale buffer
SMPEG_play(mp3);
while ((readLen = SMPEG_playAudio(mp3, ChunkBuffer, Mp3ChunkLen)) > 0)
{
MP3Length += readLen;
}
SMPEG_stop(mp3);
if(MP3Length > 0)
{
// Reallocate the buffer with the new length (if needed)
if (MP3Length != Mp3ChunkLen)
{
ChunkBuffer = (Uint8*)realloc(ChunkBuffer, MP3Length);
}
// Replay the entire MP3 into the new ChunkBuffer.
SMPEG_rewind(mp3);
SMPEG_play(mp3);
bool readBackSuccess = (MP3Length == SMPEG_playAudio(mp3, ChunkBuffer, MP3Length));
SMPEG_stop(mp3);
if(readBackSuccess)
{
// !!! Here, ChunkBuffer contains only zeros !!!
success = true;
}
}
}
SMPEG_delete(mp3);
mp3 = NULL;
}
SDL_RWclose(mp3File);
mp3File = NULL;
}
free(ChunkBuffer);
return success;
}
该代码广泛基于 SDL_Mixer,由于其局限性,我无法将其用于我的项目。
我知道 Ogg Vorbis 是更好的文件格式选择,但我正在移植一个非常古老的项目,它完全适用于 MP3。
我确定音响系统已正确初始化,因为我可以很好地播放 WAV 文件。它以 44100 的频率、2 个通道、1024 个样本和 AUDIO_S16SYS 格式(据我从 SMPEG 源中了解到,后者是强制性的)进行初始化。
我已经根据比特率、MP3 中的数据量和 OpenAudioDevice 音频规格计算了预期的缓冲区大小,一切都是一致的。
我不明白为什么除了缓冲区数据之外的所有东西似乎都在工作。
更新 #1
仍在尝试找出问题所在,我认为对 MP3 的支持可能无法正常工作,所以我创建了以下函数:
SMPEG *mpeg;
SMPEG_Info info;
mpeg = SMPEG_new(filename,&info, 1);
SMPEG_play(mpeg);
do { SDL_Delay(50); } while(SMPEG_status(mpeg) == SMPEG_PLAYING);
SMPEG_delete(mpeg);
播放了 MP3。所以,解码实际上应该是有效的。但这不是我需要的;我真的需要声音缓冲区数据,所以我可以将它发送到我的调音台。
经过大量修改、研究和挖掘 SMPEG 源代码后,我意识到我必须将 1 作为 SDLAudio 参数传递给 SMPEG_new_rwops 函数。
smpeg.h 中的评论具有误导性:
The sdl_audio parameter indicates if SMPEG should initialize the SDL audio subsystem. If not, you will have to use the SMPEG_playaudio() function below to extract the decoded data.
由于音频子系统已经初始化并且我正在使用 SMPEG_playaudio() 函数,所以我没有理由认为我需要此参数为非零。在 SMPEG 中,此参数会在打开时触发音频解压缩,但即使我调用 SMPEG_enableaudio(mp3, 1);
,数据也永远不会重新解析。这可能是一个 bug/a 可疑的功能。
由于我自己释放了 SDL_RWops 对象,因此 freesrc 参数有另一个问题,该参数需要为 0。
供以后参考,一旦 ChunkBuffer 有了 MP3 数据,如果要通过已经打开的音频设备播放,则需要通过 SDL_BuildAudioCVT/SDL_ConvertAudio。
最终的工作代码是:
// bool ReadMP3ToBuffer(char* filename)
bool success = false;
const Uint32 Mp3ChunkLen = 4096;
SDL_AudioSpec mp3Specs;
SMPEG* mp3;
SMPEG_Info infoMP3;
Uint8 * ChunkBuffer;
Uint32 MP3Length = 0;
// Allocate a chunk buffer
ChunkBuffer = (Uint8*)malloc(Mp3ChunkLen);
memset(ChunkBuffer, 0, Mp3ChunkLen);
SDL_RWops *mp3File = SDL_RWFromFile(filename, "rb"); // filename is a char* passed to the function.
if (mp3File != NULL)
{
mp3 = SMPEG_new_rwops(mp3File, &infoMP3, 0, 1);
if(mp3 != NULL)
{
if(infoMP3.has_audio)
{
Uint32 readLen;
// Get the MP3 audio specs for later conversion
SMPEG_wantedSpec(mp3, &mp3Specs);
SMPEG_enablevideo(mp3, 0);
// Play the MP3 once to get the size of the needed buffer in relation with the audio specs
SMPEG_play(mp3);
while ((readLen = SMPEG_playAudio(mp3, ChunkBuffer, Mp3ChunkLen)) > 0)
{
MP3Length += readLen;
}
SMPEG_stop(mp3);
if(MP3Length > 0)
{
// Reallocate the buffer with the new length (if needed)
if (MP3Length != Mp3ChunkLen)
{
ChunkBuffer = (Uint8*)realloc(ChunkBuffer, MP3Length);
memset(ChunkBuffer, 0, MP3Length);
}
// Replay the entire MP3 into the new ChunkBuffer.
SMPEG_rewind(mp3);
SMPEG_play(mp3);
bool readBackSuccess = (MP3Length == SMPEG_playAudio(mp3, ChunkBuffer, MP3Length));
SMPEG_stop(mp3);
if(readBackSuccess)
{
SDL_AudioCVT convertedSound;
// NOTE : static SDL_AudioSpec asDeviceSpecs; containing valid values after a call to SDL_OpenAudioDevice
if(SDL_BuildAudioCVT(&convertedSound, mp3Specs.format, mp3Specs.channels, mp3Specs.freq, asDeviceSpecs.format, asDeviceSpecs.channels, asDeviceSpecs.freq) >= 0)
{
Uint32 newBufferLen = MP3Length*convertedSound.len_mult;
// Make sure the audio length is a multiple of a sample size to avoid sound clicking
int sampleSize = ((asDeviceSpecs.format & 0xFF)/8)*asDeviceSpecs.channels;
newBufferLen &= ~(sampleSize-1);
// Allocate the new buffer and proceed with the actual conversion.
convertedSound.buf = (Uint8*)malloc(newBufferLen);
memcpy(convertedSound.buf, ChunkBuffer, MP3Length);
convertedSound.len = MP3Length;
if(SDL_ConvertAudio(&convertedSound) == 0)
{
// Save convertedSound.buf and convertedSound.len_cvt for future use in your mixer code.
// Dont forget to free convertedSound.buf once it's not used anymore.
success = true;
}
}
}
}
}
SMPEG_delete(mp3);
mp3 = NULL;
}
SDL_RWclose(mp3File);
mp3File = NULL;
}
free(ChunkBuffer);
return success;
注意 :当我用这段代码对它们重新采样时,我尝试过的一些 MP3 文件丢失了几毫秒并且在播放过程中过早中断。其他一些则没有。我可以在 Audacity 中重现相同的行为,所以我不确定发生了什么。我的代码可能仍然存在错误,SMPEG 中的错误,或者可能是 MP3 格式本身的已知问题。如果有人可以在评论中提供和解释,那就太好了!