由bw1024答案有點基礎,我創造了這個完整的Ogg Vorbis播放器libvorbisfile。
它擴展了以前的答案,演示瞭如何使用函數來填充音頻緩衝區(如,不會自己生成聲音),並在事件偵聽器回調中添加重放結束檢測。
代碼本身受到很多評論,希望能夠解釋需要解釋的所有內容。
我試圖保持它與音頻隊列和libvorbisfile的「製作質量」接近,因此它包含「真實」的錯誤條件並檢查特殊情況;例如vorbis文件中的變量採樣率,它無法處理。
我希望所有的噪音都不會影響它作爲樣本的價值。
// vorplay.c - by Johann `Myrkraverk' Oskarsson <[email protected]>
// In the interest of example code, it's explicitly licensed under the
// WTFPL, see the bottom of the file or http://www.wtfpl.net/ for details.
#include <pthread.h> // For pthread_exit().
#include <vorbis/vorbisfile.h>
#include <AudioToolbox/AudioToolbox.h>
#include <stdio.h>
// This is a complete example of an Ogg Vorbis player based on the vorbisfile
// library and the audio queue API in OS X.
// It can be either taken as an example of how to use libvorbisfile, or
// audio queue programming.
// There are many "magic number" constants in the code, and understanding
// them requires looking up the relevant documentation. Some, such as
// the number of buffers in the audio queue and the size of each buffer
// are the result of experimentation. A "real application" may benefit
// from allowing the user to tweak these, in order to resolve audio stutters.
// Error handling is done very simply in order to focus on the example code
// while still resembling "production code." Here, we use the
// if (status = foo()) { ... }
// syntax for error checking. The assignment in if()s is not an error.
// If your compiler is complaining, you can use its equivalent of the
// GCC switch -Wno-parentheses to silence it.
// Assuming you'll want to use libvorbisfile from mac ports, you can
// compile it like this.
// gcc -c -I/opt/local/include \
// vorplay.c \
// -Wno-parentheses
// And link with
// gcc -o vorplay vorplay.o \
// -L/opt/local/lib -lvorbisfile \
// -framework AudioToolbox
// The start/stop listener...
void listener(void *vorbis, AudioQueueRef queue, AudioQueuePropertyID id)
{
// Here, we're only listening for start/stop, so don't need to check
// the id; it's always kAudioQueueProperty_IsRunning in our case.
UInt32 running = 0;
UInt32 size = sizeof running;
/* OggVorbis_File *vf = (OggVorbis_File *) vorbis; */
OSStatus status = -1;
if (status = AudioQueueGetProperty(queue, id, &running, &size)) {
printf("AudioQueueGetProperty status = %d; running = %d\n",
status, running);
exit(1);
}
if (!running) {
// In a "real example" we'd clean up the vf pointer with ov_clear() and
// the audio queue with AudioQueueDispose(); however, the latter is
// better not called from within the listener function, so we just
// exit normally.
exit(0);
// In a "real" application, we might signal the termination with
// a pthread condition variable, or something similar, instead;
// where the waiting thread would call AudioQueueDispose(). It is
// "safe" to call ov_clear() here, but there's no point.
}
}
// The audio queue callback...
void callback(void *vorbis, AudioQueueRef queue, AudioQueueBufferRef buffer)
{
OggVorbis_File *vf = (OggVorbis_File *) vorbis;
int section = 0;
OSStatus status = -1;
// The parameters here are congruent with our format specification for
// the audio queue. We read directly into the audio queue buffer.
long r = ov_read(vf, buffer->mAudioData, buffer->mAudioDataBytesCapacity,
0, 2, 1, §ion);
// As an extra precaution, check if the current buffer is the same sample
// rate and channel number as the audio queue.
{
vorbis_info *vinfo = ov_info(vf, section);
if (vinfo == NULL) {
printf("ov_info status = NULL\n");
exit(1);
}
AudioStreamBasicDescription description;
UInt32 size = sizeof description;
if (status = AudioQueueGetProperty(queue,
kAudioQueueProperty_StreamDescription,
&description,
&size)) {
printf("AudioQueueGetProperty status = %d\n", status);
exit(1);
}
// If we were using some other kind of audio playback API, such as OSS4
// we could simply change the sample rate and channel number on the fly.
// However, with an audio queue, we'd have to use a different
// one, afaict; so we don't handle it at all in this example.
if (vinfo->rate != description.mSampleRate) {
printf("We don't handle changes in sample rate.\n");
exit(1);
}
if (vinfo->channels != description.mChannelsPerFrame) {
printf("We don't handle changes in channel numbers.\n");
exit(1);
}
}
// The real "callback"...
if (r == 0) { // No more data, stop playing.
// Flush data, to make sure we play to the end.
if (status = AudioQueueFlush(queue)) {
printf("AudioQueueFlush status = %d\n", status);
exit(1);
}
// Stop asynchronously.
if (status = AudioQueueStop(queue, false)) {
printf("AudioQueueStop status = %d\n", status);
exit(1);
}
} else if (r < 0) { // Some error?
printf("ov_read status = %ld\n", r);
exit(1);
} else { // The normal course of action.
// ov_read() may not return exactly the number of bytes we requested.
// so we update the buffer size per call.
buffer->mAudioDataByteSize = r;
if (status = AudioQueueEnqueueBuffer(queue, buffer, 0, 0)) {
printf("AudioQueueEnqueueBuffer status = %d, r = %ld\n", status, r);
exit(1);
}
}
}
int main(int argc, char *argv[])
{
// The very simple command line argument check.
if (argc != 2) {
printf("Usage: vorplay <file>\n");
exit(1);
}
FILE *file = fopen(argv[ 1 ], "r");
if (file == NULL) {
printf("Unable to open input file.\n");
exit(1);
}
OggVorbis_File vf;
// Using OV_CALLBACKS_DEFAULT means ov_clear() will close the file.
// However, this particular example doesn't use that function.
// A typical place for it might be the listener(), when we stop
// playing.
if (ov_open_callbacks(file, &vf, 0, 0, OV_CALLBACKS_DEFAULT)) {
printf("ov_open_callbacks() failed. Not an Ogg Vorbis file?\n");
exit(1);
}
// For the sample rate and channel number in the audio.
vorbis_info *vinfo = ov_info(&vf, -1);
if (vinfo == NULL) {
printf("ov_info status = NULL\n");
exit(1);
}
// The audio queue format specification. This structure must be set
// to values congruent to the ones we use with ov_read().
AudioStreamBasicDescription format = { 0 };
// First, the constants. The format is quite hard coded, both here
// and in the calls to ov_read().
format.mFormatID = kAudioFormatLinearPCM;
format.mFormatFlags =
kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
format.mFramesPerPacket = 1;
format.mBitsPerChannel = 16;
// Load the sample rate and channel number from the vorbis file.
format.mSampleRate = vinfo->rate;
format.mChannelsPerFrame = vinfo->channels;
// The number of bytes depends on the channel number.
format.mBytesPerPacket =
format.mBytesPerFrame = 2 * vinfo->channels; // times two, for 16bit
OSStatus status = -1;
AudioQueueRef queue;
// Create the audio queue with the desired format. Notice that we
// use the OggVorbis_File pointer as the data far the callback.
if (status = AudioQueueNewOutput(&format, callback,
&vf, NULL, NULL, 0, &queue)) {
printf("AudioQueueNewOutput status = %d\n", status);
exit(1);
}
// For me distortions happen with 3 buffers; hence the magic number 5.
AudioQueueBufferRef buffers[ 5 ];
for (int i = 0; i < sizeof buffers/sizeof (AudioQueueBufferRef); i++) {
// For each buffer...
// The size of the buffer is a magic number. 4096 is good enough, too.
if (status = AudioQueueAllocateBuffer(queue, 8192, &buffers[ i ])) {
printf("AudioQueueAllocateBuffer status = %d\n", status);
exit(1);
}
// Enqueue buffers, before play. According to the process outlined
// in the Audio Queue Services Programming Guide, we must do this
// before calling AudioQueueStart() and it's simplest to do it like
// this.
callback(&vf, queue, buffers[ i ]);
}
// We set the volume to maximum; even though the docs say it's the
// default.
if (status = AudioQueueSetParameter(queue,
kAudioQueueParam_Volume, 1.0)) {
printf("AudioQueueSetParameter status = %d\n", status);
exit(1);
}
// Here, we might want to call AudioQueuePrime if we were playing one
// of the supported compressed formats. However, since we only have
// raw PCM buffers to play, I don't see the point. Maybe playing will
// start faster with it, after AudioQueueStart() but I still don't see
// the point for this example; if there's a delay, it'll happen anyway.
// We add a listener for the start/stop event, so we know when to call
// exit(0) and terminate the application. We also give it the vf
// pointer, even though it's not used in our listener().
if (status = AudioQueueAddPropertyListener(queue,
kAudioQueueProperty_IsRunning,
listener,
&vf)) {
printf("AudioQueueAddPropertyListener status = %d\n", status);
exit(1);
}
// And then start to play the file.
if (status = AudioQueueStart(queue, 0)) {
printf("AudioQueueStart status = %d\n", status);
exit(1);
}
// Work's For Me[tm]. This trick to make sure the process doesn't
// terminate before the song has played "works for me" on
// OS X 10.10.3. If you're going to use this same trick in production
// code, you might as well turn off the joinability of the main thread,
// with pthread_detach() and also make sure no callback or listener is
// using data from the stack. Unlike this example.
pthread_exit(0);
return 0; // never reached, left intact in case some compiler complains.
}
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyright (C) 2015 Johann `Myrkraverk' Oskarsson
// <[email protected]>
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.