Skip to content

Instantly share code, notes, and snippets.

@taotao54321
Created September 1, 2016 10:47
Show Gist options
  • Save taotao54321/18d213dd72528417da866c0b26c6291d to your computer and use it in GitHub Desktop.
Save taotao54321/18d213dd72528417da866c0b26c6291d to your computer and use it in GitHub Desktop.
/**
* 正弦波を出力する。引数でバッファサイズを指定
* (※SDLのドキュメントでは AudioSpec::samples はサンプル数って書いて
* るけど、実際はバッファサイズだと思う)
*
* メインスレッドで音を溜めてコールバックで出力する方式
* スレッド間のデータのやりとりに boost::lockfree::spsc_queue を使用
*/
#include <string>
#include <array>
#include <algorithm>
#include <stdexcept>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstdint>
#include <cassert>
#include <boost/lockfree/spsc_queue.hpp>
#include <SDL.h>
using namespace std;
namespace{
constexpr int FPS = 60;
constexpr int SPEC_FREQ = 44100;
constexpr SDL_AudioFormat SPEC_FORMAT = AUDIO_S16SYS;
constexpr int SPEC_CHANNELS = 1;
constexpr int SIN_AMP = 16000;
constexpr int SIN_FREQ = 440;
bool is_pow2(unsigned int n)
{
if(n == 0) return false;
return (n & (n-1)) == 0;
}
void warn(const char* msg)
{
fputs(msg, stderr);
putc('\n', stderr);
}
void error(const char* msg)
{
warn(msg);
exit(1);
}
void print_spec(const SDL_AudioSpec& spec, bool obtained)
{
printf("freq : %d\n", spec.freq);
printf("format : %d\n", spec.format);
printf("channels : %u\n", spec.channels);
printf("samples : %u\n", spec.samples);
if(obtained){
printf("silence : %u\n", spec.silence);
printf("size : %u\n", spec.size);
}
}
boost::lockfree::spsc_queue<
int16_t,
boost::lockfree::capacity<8*(SPEC_FREQ/FPS)> // 多めに取らないとunderflow発生
> audio_queue;
void callback(void* /*userdata*/, uint8_t* stream, int len)
{
assert(!(len&1));
//printf("%d\n", len);
unsigned int n_sample = len / 2;
int16_t* p = reinterpret_cast<int16_t*>(stream);
size_t n_pop = audio_queue.pop(p, n_sample);
if(n_pop < n_sample){
printf("UNDERFLOW: %lu samples\n", n_sample - n_pop);
fill_n(p+n_pop, 0, n_sample-n_pop);
}
}
void usage()
{
error("audio2 <samples>");
}
}
int main(int argc, char** argv)
{
if(argc != 2) usage();
unsigned long samples;
try{
samples = stoul(argv[1]);
}
catch(const logic_error& e){
error("invalid samples value");
}
if(samples > 0x8000) error("samples too large");
if(samples < 64) error("samples too small"); // 小さすぎるとハングするっぽい
if(!is_pow2(samples)) error("samples must be power of 2");
if(SDL_Init(SDL_INIT_AUDIO | SDL_INIT_TIMER) != 0) error("SDL_Init() failed");
atexit(SDL_Quit);
{
const char* driver = SDL_GetCurrentAudioDriver();
printf("Audio driver: %s\n", driver ? driver : "(null!?)");
puts("");
}
SDL_AudioSpec want = {}; // これで警告出るの納得いかない…
want.freq = SPEC_FREQ;
want.format = SPEC_FORMAT;
want.channels = SPEC_CHANNELS;
want.samples = samples;
want.callback = callback;
puts("[Desired spec]");
print_spec(want, false);
puts("");
SDL_AudioSpec have;
SDL_AudioDeviceID dev = SDL_OpenAudioDevice(nullptr, 0, &want, &have, 0);
if(!dev) error("SDL_OpenAudioDevice() failed");
// samples は指定通りになるとは限らない
if(want.freq != have.freq ||
want.format != have.format ||
want.channels != have.channels) error("spec changed");
puts("[Obtained spec]");
print_spec(have, true);
puts("");
SDL_PauseAudioDevice(dev, 0);
unsigned int t = 0;
array<int16_t, SPEC_FREQ/FPS> buf;
for(;;){
// write_available() はバージョンが古いと使えない?
//size_t size = min(buf.size(), audio_queue.write_available());
unsigned int n_written = 0;
while(n_written < buf.size()){
int16_t sample = SIN_AMP * sin(2*M_PI * SIN_FREQ/SPEC_FREQ * t);
if(audio_queue.push(sample)){
++n_written;
++t;
}
else{
printf("OVERFLOW: %lu samples\n", buf.size() - n_written);
break;
}
}
// 書き込みが追いつかないとunderflowして音がブツ切れになるので
// ウェイトは少し減らす。overflowする分には問題ない
SDL_Delay(1000/FPS - 5);
}
SDL_CloseAudioDevice(dev);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment