My goal is to connect the RPM of an engine to the pitch of an sound. I am using SDL as my audio Backend.
So my idea was to sample from the wave buffer quicker than normal. So by trail and error I am now able to pitch my engine sound "step by step".
Question #1
If I change this part from:
audioBuff += 1 + pitch * 2;
to
audioBuff += 2
I get just noise. Why? Does this have to do with stereo channels?
Question #2
How can I make this a linear pitch? Currently it's a "stepping" pitch.
Here is the full code:
#include "SDL2/SDL.h"
#include <iostream>
void audioCallback(void* userdata, Uint8 *stream, int len);
Uint8 *audioBuff = nullptr;
Uint8 *audioBuffEnd = nullptr;
Uint32 audioLen = 0;
bool quit = false;
Uint16 pitch = 0;
int main()
{
if(SDL_Init(SDL_INIT_AUDIO) < 0)
return -1;
Uint32 wavLen = 0;
Uint8 *wavBuff = nullptr;
SDL_AudioSpec wavSpec;
if(SDL_LoadWAV("test.wav", &wavSpec, &wavBuff, &wavLen) == nullptr)
{
return 1;
}
wavSpec.callback = audioCallback;
wavSpec.userdata = nullptr;
wavSpec.format = AUDIO_S16;
wavSpec.samples = 2048;
audioBuff = wavBuff;
audioBuffEnd = &wavBuff[wavLen];
audioLen = wavLen;
if( SDL_OpenAudio(&wavSpec, NULL) < 0)
{
fprintf(stderr, "Could not open audio: %s\n", SDL_GetError());
return 1;
}
SDL_PauseAudio(0);
while(!quit)
{
SDL_Delay(500);
pitch ++;
}
SDL_CloseAudio();
SDL_FreeWAV(wavBuff);
return 0;
}
Uint32 sampleIndex = 0;
void audioCallback(void* userdata, Uint8 *stream, int len)
{
Uint32 length = (Uint32)len;
length = (length > audioLen ? audioLen : length);
for(Uint32 i = 0; i < length; i++)
{
if(audioBuff > audioBuffEnd)
{
quit = true;
return;
}
// why pitch * 2?
// how to get a smooth pitch?
stream[i] = audioBuff[0];
audioBuff += 1 + pitch * 2;
fprintf(stdout, "pitch: %u\n", pitch);
}
}
You're setting the audio format to AUDIO_S16, which is "Signed 16-bit little-endian samples". Each sample is two bytes, with the first byte being the LSB. When you read the data in audioCallback, you're reading it as bytes (8 bits), then passing those bytes back to something expecting 16 bits. You're getting noise because of this, and when you use audioBuff +=2; you're always reading the LSB of the audio sample, which essentially is noise when used that way.
You should consistently use either 16 bit or 8 bit samples.
Related
I am trying to generate a simple, constant sine tone using SDL_audio. I have a small helper class that can be called to turn the tone on/off, change the frequency, and change the wave shape. I have followed some examples I could find on the web and got the following:
beeper.h
#pragma once
#include <SDL.h>
#include <SDL_audio.h>
#include <cmath>
#include "logger.h"
class Beeper {
private:
//Should there be sound right now
bool soundOn = true;
//Type of wave that should be generated
int waveType = 0;
//Tone that the wave will produce (may or may not be applicable based on wave type)
float waveTone = 440;
//Running index for sampling
float samplingIndex = 0;
//These are useful variables that cannot be changed outside of this file:
//Volume
const Sint16 amplitude = 32000;
//Sampling rate
const int samplingRate = 44100;
//Buffer size
const int bufferSize = 1024;
//Samples a sine wave at a given index
float sampleSine(float index);
//Samples a square wave at a given index
float sampleSquare(float index);
public:
//Initializes SDL audio, audio device, and audio specs
void initializeAudio();
//Function called by SDL audio_callback that fills stream with samples
void generateSamples(short* stream, int length);
//Turn sound on or off
void setSoundOn(bool soundOnOrOff);
//Set timbre of tone produced by beeper
void setWaveType(int waveTypeID);
//Set tone (in Hz) produced by beeper
void setWaveTone(int waveHz);
};
beeper.cpp
#include <beeper.h>
void fillBuffer(void* userdata, Uint8* _stream, int len) {
short * stream = reinterpret_cast<short*>(_stream);
int length = len;
Beeper* beeper = (Beeper*)userdata;
beeper->generateSamples(stream, length);
}
void Beeper::initializeAudio() {
SDL_AudioSpec desired, returned;
SDL_AudioDeviceID devID;
SDL_zero(desired);
desired.freq = samplingRate;
desired.format = AUDIO_S16SYS; //16-bit audio
desired.channels = 1;
desired.samples = bufferSize;
desired.callback = &fillBuffer;
desired.userdata = this;
devID = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(0,0), 0, &desired, &returned, SDL_AUDIO_ALLOW_FORMAT_CHANGE);
SDL_PauseAudioDevice(devID, 0);
}
void Beeper::generateSamples(short *stream, int length) {
int samplesToWrite = length / sizeof(short);
for (int i = 0; i < samplesToWrite; i++) {
if (soundOn) {
if (waveType == 0) {
stream[i] = (short)(amplitude * sampleSine(samplingIndex));
}
else if (waveType == 1) {
stream[i] = (short)(amplitude * 0.8 * sampleSquare(samplingIndex));
}
}
else {
stream[i] = 0;
}
//INFO << "Sampling index: " << samplingIndex;
samplingIndex += (waveTone * M_PI * 2) / samplingRate;
//INFO << "Stream input: " << stream[i];
if (samplingIndex >= (M_PI*2)) {
samplingIndex -= M_PI * 2;
}
}
}
void Beeper::setSoundOn(bool soundOnOrOff) {
soundOn = soundOnOrOff;
//if (soundOnOrOff) {
// samplingIndex = 0;
//}
}
void Beeper::setWaveType(int waveTypeID) {
waveType = waveTypeID;
//samplingIndex = 0;
}
void Beeper::setWaveTone(int waveHz) {
waveTone = waveHz;
//samplingIndex = 0;
}
float Beeper::sampleSine(float index) {
double result = sin((index));
//INFO << "Sine result: " << result;
return result;
}
float Beeper::sampleSquare(float index)
{
int unSquaredSin = sin((index));
if (unSquaredSin >= 0) {
return 1;
}
else {
return -1;
}
}
The callback function is being called and the generateSamples function is loading data into the stream, but I cannot hear anything but a very slight click at irregular periods. I have had a look at the data inside the stream and it follows a pattern that I would expect for a scaled sine wave with a 440 Hz frequency. Is there something obvious that I am missing? I did notice that the size of the stream is double what I put when declaring the SDL_AudioSpec and calling SDL_OpenAudioDevice. Why is that?
Answered my own question! When opening the audio device I used the flag SDL_AUDIO_ALLOW_FORMAT_CHANGE which meant that SDL was actually using a float buffer instead of the short buffer that I expected. This was causing issues in a couple of places that were hard to detect (the stream being double the amount of bytes I was expecting should have tipped me off). I changed that parameter in SDL_OpenAudioDevice() to 0 and it worked as expected!
I can't seem to understand why this doesn't work I’m trying to get a sound sample to a given function.
I've based my code on a version of the function that uses objective-c which works.
But the code below written in C++ doesn't it meant to function by passing a float buffer to OXY_DecodeAudioBuffer function that latter tries and looks for data in the buffer.
Question: Am I passing the right buffer size and output from the buffer to the function? I always get no data found in buffer. Can anyone see the issue?
The hardware I'm using is Raspberry Pi 2 with a USB microphone.
I’ve also included the function with the description:
//OXY_DecodeAudioBuffer function, receives an audiobuffer of specified size and outputs if encoded data is found
//* Parameters:
// audioBuffer: float array of bufferSize size with audio data to be decoded
// size: size of audioBuffer
// oxyingObject: OXY object instance, created in OXY_Create()
//* Returns: -1 if no decoded data is found, -2 if start token is found, -3 if complete word has been decoded, positive number if character is decoded (number is the token idx)
OXY_DLLEXPORT int32_t OXY_DecodeAudioBuffer(float *audioBuffer, int size, void *oxyingObject);
The float_buffer output from the code below:
1. -0.00354004 -0.00369263 -0.00338745 -0.00354004 -0.00341797 -0.00402832
Program Code:
#include <stdio.h>
#include <stdlib.h>
#include <alsa/asoundlib.h>
#include <unistd.h>
#include <math.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include "Globals.h"
#include "OxyCoreLib_api.h"
void* mCore;
using namespace std;
void GetDecodedMode(){
std::cerr << "DECODE_MODE ---> " << OXY_GetDecodedMode(mCore) << std::endl << std::endl;
}
int main(void)
{
int i,j;
int err;
int mode = 3;
int16_t *buffer;
float* float_buffer;
// Allocate our own buffers (1 channel, 16 bits per sample, thus 16 bits per frame, thus 2 bytes per frame).
// Practice learns the buffers used contain 512 frames, if this changes it will be fixed in processAudio.
int buffer_frames = 512; //Not sure this correct but reason above
unsigned int rate = 44100;
float sampleRate = 44100.f; //to configure
snd_pcm_t *capture_handle;
snd_pcm_hw_params_t *hw_params;
snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;
if ((err = snd_pcm_open(&capture_handle, "hw:1,0", SND_PCM_STREAM_CAPTURE, 0)) < 0) {
fprintf(stderr, "cannot open audio device %s (%s)\n","device",snd_strerror(err));
exit(1);
} else {fprintf(stdout, "audio interface opened\n");}
if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
fprintf(stderr, "cannot allocate hardware parameter structure (%s)\n",
snd_strerror(err));
exit(1);
} else { fprintf(stdout, "hw_params allocated\n"); }
if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) {
fprintf(stderr, "cannot initialize hardware parameter structure (%s)\n",
snd_strerror(err));
exit(1);
} else { fprintf(stdout, "hw_params initialized\n"); }
if ((err = snd_pcm_hw_params_set_access(capture_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
fprintf(stderr, "cannot set access type (%s)\n",
snd_strerror(err));
exit(1);
} else { fprintf(stdout, "hw_params access set\n"); }
if ((err = snd_pcm_hw_params_set_format(capture_handle, hw_params, format)) < 0) {
fprintf(stderr, "cannot set sample format (%s)\n",
snd_strerror(err));
exit(1);
} else { fprintf(stdout, "hw_params format set\n"); }
if ((err = snd_pcm_hw_params_set_rate_near(capture_handle, hw_params, &rate, 0)) < 0) {
fprintf(stderr, "cannot set sample rate (%s)\n",
snd_strerror(err));
exit(1);
} else { fprintf(stdout, "hw_params rate set\n"); }
if ((err = snd_pcm_hw_params_set_channels(capture_handle, hw_params, 1)) < 0) {
fprintf(stderr, "cannot set channel count (%s)\n",
snd_strerror(err));
exit(1);
} else { fprintf(stdout, "hw_params channels set\n"); }
if ((err = snd_pcm_hw_params(capture_handle, hw_params)) < 0) {
fprintf(stderr, "cannot set parameters (%s)\n",
snd_strerror(err));
exit(1);
} else { fprintf(stdout, "hw_params set\n"); }
snd_pcm_hw_params_free(hw_params);
fprintf(stdout, "hw_params freed\n");
if ((err = snd_pcm_prepare(capture_handle)) < 0) {
fprintf(stderr, "cannot prepare audio interface for use (%s)\n",
snd_strerror(err));
exit(1);
} else { fprintf(stdout, "audio interface prepared\n"); }
//allocate buffer of 16bit ints, as specified in PCM_FORMAT
//initialise
mCore = OXY_Create();
//Configure - Mode 3 inaudible, 44100, bufferSize
OXY_Configure(mode, sampleRate, buffer_frames, mCore);
//Debug to make sure
GetDecodedMode();
buffer = static_cast<int16_t*>(malloc(buffer_frames * snd_pcm_format_width(format) / 8 * 2));
//buffer = malloc(buffer_frames * snd_pcm_format_width(format) / 8 * 2);
float_buffer = static_cast<float*>(malloc(buffer_frames*sizeof(float)));
//float_buffer = malloc(buffer_frames*sizeof(float));
fprintf(stdout, "buffer allocated\n");
//where did 10000 come from doubt its correct
for (i = 0; i < 10000; ++i) {
//read from audio device into buffer
if ((err = snd_pcm_readi(capture_handle, buffer, buffer_frames)) != buffer_frames) {
fprintf(stderr, "read from audio interface failed (%s)\n",
err, snd_strerror(err));
exit(1);
}
//try to change buffer from short ints to floats for transformation
for (i = 0; i < buffer_frames; i++){
//norm
float_buffer[i] = (float)buffer[i]/32768.0;
//Example output of float_buffer
/*
-0.00354004
-0.00369263
-0.00338745
-0.00354004
-0.00341797
-0.00402832
-0.00341797
-0.00427246
-0.00375366
-0.00378418
-0.00408936
-0.00332642
-0.00369263
-0.00350952
-0.00369263
-0.00369263
-0.00344849
-0.00354004
*/
}
//send to float_to be tested
int ret = OXY_DecodeAudioBuffer(float_buffer, buffer_frames, mCore);
if (ret == -2)
{
std::cerr << "FOUND_TOKEN ---> -2 " << std::endl << std::endl;
}
else if(ret>=0)
{
std::cerr << "Decode started ---> -2 " << ret << std::endl << std::endl;
}
else if (ret == -3)
{
//int sizeStringDecoded = OXY_GetDecodedData(mStringDecoded, mCore);
std::cerr << "STRING DECODED ---> -2 " << std::endl << std::endl;
// ...
}
else
{
std::cerr << "No data found in this buffer" << std::endl << std::endl;
//no data found in this buffer
}
}
free(buffer);
snd_pcm_close(capture_handle);
std::cerr << "memory freed\n" << std::endl << std::endl;
//snd_pcm_close(capture_handle);
return(0);
//exit(0);
}
Working objective-c version using the same API:
//
// IosAudioController.m
//
#import "IosAudioController.h"
#import <AudioToolbox/AudioToolbox.h>
#import "OxyCoreLib_api.h"
#define kOutputBus 0
#define kInputBus 1
IosAudioController* iosAudio;
void checkStatus(int status){
if (status) {
printf("Status not 0! %d\n", status);
exit(1);
}
}
static OSStatus recordingCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData) {
if (iosAudio->mOxyObject->mDecoding == 0)
return noErr;
// Because of the way our audio format (setup below) is chosen:
// we only need 1 buffer, since it is mono
// Samples are 16 bits = 2 bytes.
// 1 frame includes only 1 sample
AudioBuffer buffer;
buffer.mNumberChannels = 1;
buffer.mDataByteSize = inNumberFrames * 2;
buffer.mData = malloc( inNumberFrames * 2 );
// Put buffer in a AudioBufferList
AudioBufferList bufferList;
bufferList.mNumberBuffers = 1;
bufferList.mBuffers[0] = buffer;
// Then:
// Obtain recorded samples
OSStatus status;
status = AudioUnitRender([iosAudio audioUnit],
ioActionFlags,
inTimeStamp,
inBusNumber,
inNumberFrames,
&bufferList);
checkStatus(status);
// Now, we have the samples we just read sitting in buffers in bufferList
// Process the new data
[iosAudio processAudio:&bufferList];
//Now Decode Audio *******************
//convert from AudioBuffer format to *float buffer
iosAudio->floatBuffer = (float *)malloc(inNumberFrames * sizeof(float));
//UInt16 *frameBuffer = bufferList.mBuffers[0].mData;
SInt16 *frameBuffer = bufferList.mBuffers[0].mData;
for(int j=0;j<inNumberFrames;j++)
{
iosAudio->floatBuffer[j] = frameBuffer[j]/32768.0;
}
int ret = OXY_DecodeAudioBuffer(iosAudio->floatBuffer, inNumberFrames, (void*)iosAudio->mOxyObject->mOxyCore);
if (ret == -2)
{
// NSLog(#"BEGIN TOKEN FOUND!");
[iosAudio->mObject performSelector:iosAudio->mSelector withObject:[NSNumber numberWithInt:0]];
}
else if (ret >= 0)
{
NSLog(#"Decode started %#",#(ret).stringValue);
}
else if (ret == -3)
{
int sizeStringDecoded = OXY_GetDecodedData(iosAudio->mStringDecoded, (void*)iosAudio->mOxyObject->mOxyCore);
NSString *tmpString = [NSString stringWithUTF8String:iosAudio->mStringDecoded];
iosAudio->mOxyObject->mDecodedString = [NSString stringWithUTF8String:iosAudio->mStringDecoded];
if (sizeStringDecoded > 0)
{
iosAudio->mOxyObject->mDecodedOK = 1;
NSLog(#"Decoded OK! %# ", tmpString);
[iosAudio->mObject performSelector:iosAudio->mSelector withObject:[NSNumber numberWithInt:1]];
}
else
{
iosAudio->mOxyObject->mDecodedOK = -1;
NSLog(#"END DECODING BAD! %# ", tmpString);
[iosAudio->mObject performSelector:iosAudio->mSelector withObject:[NSNumber numberWithInt:2]];
}
}
else
{
//no data found in this buffer
}
// release the malloc'ed data in the buffer we created earlier
free(bufferList.mBuffers[0].mData);
free(iosAudio->floatBuffer);
return noErr;
}
static OSStatus playbackCallback(void *inRefCon,
AudioUnitRenderActionFlags *ioActionFlags,
const AudioTimeStamp *inTimeStamp,
UInt32 inBusNumber,
UInt32 inNumberFrames,
AudioBufferList *ioData) {
// Notes: ioData contains buffers (may be more than one!)
// Fill them up as much as you can. Remember to set the size value in each buffer to match how
// much data is in the buffer.
for (int i=0; i < ioData->mNumberBuffers; i++)
{ // in practice we will only ever have 1 buffer, since audio format is mono
AudioBuffer buffer = ioData->mBuffers[i];
// NSLog(#" Buffer %d has %d channels and wants %d bytes of data.", i, buffer.mNumberChannels, buffer.mDataByteSize);
// copy temporary buffer data to output buffer
UInt32 size = min(buffer.mDataByteSize, [iosAudio tempBuffer].mDataByteSize); // dont copy more data than we have, or than fits
memcpy(buffer.mData, [iosAudio tempBuffer].mData, size);
buffer.mDataByteSize = size; // indicate how much data we wrote in the buffer
// uncomment to hear random noise
/*UInt16 *frameBuffer = buffer.mData;
for (int j = 0; j < inNumberFrames; j++)
frameBuffer[j] = rand();*/
// Play encoded buffer
if (iosAudio->mOxyObject->mEncoding > 0)
{
int sizeSamplesRead;
float audioBuffer[2048];
sizeSamplesRead = OXY_GetEncodedAudioBuffer(audioBuffer, (void*)iosAudio->mOxyObject->mOxyCore);
if (sizeSamplesRead == 0)
iosAudio->mOxyObject->mEncoding = 0;
SInt16 *frameBuffer = buffer.mData;
for(int j=0;j<sizeSamplesRead;j++)
{
frameBuffer[j] = audioBuffer[j]*32768.0;
}
}
else
{
SInt16 *frameBuffer = buffer.mData;
for (int j = 0; j < inNumberFrames; j++)
frameBuffer[j] = 0;
}
}
return noErr;
}
#implementation IosAudioController
#synthesize audioUnit, tempBuffer;
- (id) init {
self = [super init];
OSStatus status;
// Describe audio component
AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_RemoteIO;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
// Get component
AudioComponent inputComponent = AudioComponentFindNext(NULL, &desc);
// Get audio units
status = AudioComponentInstanceNew(inputComponent, &audioUnit);
checkStatus(status);
// Enable IO for recording
UInt32 flag = 1;
status = AudioUnitSetProperty(audioUnit,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Input,
kInputBus,
&flag,
sizeof(flag));
checkStatus(status);
// Enable IO for playback
status = AudioUnitSetProperty(audioUnit,
kAudioOutputUnitProperty_EnableIO,
kAudioUnitScope_Output,
kOutputBus,
&flag,
sizeof(flag));
checkStatus(status);
// Describe format
AudioStreamBasicDescription audioFormat;
audioFormat.mSampleRate = 44100.0;
audioFormat.mFormatID = kAudioFormatLinearPCM;
audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
audioFormat.mFramesPerPacket = 1;
audioFormat.mChannelsPerFrame = 1;
audioFormat.mBitsPerChannel = 16;
audioFormat.mBytesPerPacket = 2;
audioFormat.mBytesPerFrame = 2;
// Apply format
status = AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output,
kInputBus,
&audioFormat,
sizeof(audioFormat));
checkStatus(status);
status = AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input,
kOutputBus,
&audioFormat,
sizeof(audioFormat));
checkStatus(status);
// Set input callback
AURenderCallbackStruct callbackStruct;
callbackStruct.inputProc = recordingCallback;
callbackStruct.inputProcRefCon = (__bridge void *)self;
status = AudioUnitSetProperty(audioUnit,
kAudioOutputUnitProperty_SetInputCallback,
kAudioUnitScope_Global,
kInputBus,
&callbackStruct,
sizeof(callbackStruct));
checkStatus(status);
// Set output callback
callbackStruct.inputProc = playbackCallback;
callbackStruct.inputProcRefCon = (__bridge void *)self;
status = AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Global,
kOutputBus,
&callbackStruct,
sizeof(callbackStruct));
checkStatus(status);
// Disable buffer allocation for the recorder (optional - do this if we want to pass in our own)
flag = 0;
status = AudioUnitSetProperty(audioUnit,
kAudioUnitProperty_ShouldAllocateBuffer,
kAudioUnitScope_Output,
kInputBus,
&flag,
sizeof(flag));
// Allocate our own buffers (1 channel, 16 bits per sample, thus 16 bits per frame, thus 2 bytes per frame).
// Practice learns the buffers used contain 512 frames, if this changes it will be fixed in processAudio.
tempBuffer.mNumberChannels = 1;
int size = 512;
#if (TARGET_OS_SIMULATOR)
size = 256; //TODO check this value!! depends on play/record callback buffer size
#else
size = 512; //TODO check this value!! depends on play/record callback buffer size
#endif
tempBuffer.mDataByteSize = size * 2;
tempBuffer.mData = malloc( size * 2);
// Initialise
status = AudioUnitInitialize(audioUnit);
checkStatus(status);
return self;
}
- (void) start {
OSStatus status = AudioOutputUnitStart(audioUnit);
checkStatus(status);
}
- (void) stop {
OSStatus status = AudioOutputUnitStop(audioUnit);
checkStatus(status);
}
- (void) processAudio: (AudioBufferList*) bufferList{
AudioBuffer sourceBuffer = bufferList->mBuffers[0];
// fix tempBuffer size if it's the wrong size
if (tempBuffer.mDataByteSize != sourceBuffer.mDataByteSize) {
free(tempBuffer.mData);
tempBuffer.mDataByteSize = sourceBuffer.mDataByteSize;
tempBuffer.mData = malloc(sourceBuffer.mDataByteSize);
}
// copy incoming audio data to temporary buffer
memcpy(tempBuffer.mData, bufferList->mBuffers[0].mData, bufferList->mBuffers[0].mDataByteSize);
}
- (void) dealloc {
AudioUnitUninitialize(audioUnit);
free(tempBuffer.mData);
}
- (void) setOxyObject: (OxyCore*) oxyObject
{
mOxyObject = oxyObject;
}
- (void) setListenCallback:(id)object withSelector:(SEL)selector
{
mObject = object;
mSelector = selector;
}
#end
One problem that I can see is that you are using 2 nested loops with the same variable for iteration. The first loop for (i = 0; i < 10000; ++i) and the second one for (i = 0; i < buffer_frames; i++), if buffer_frames >= 10000 - 1 the first loop will be executed once and exit, otherwise it will enter an infinite loop.
I have two more remarks regarding the following line:
buffer = static_cast<int16_t*>(malloc(buffer_frames * snd_pcm_format_width(format) / 8 * 2));
According to the API reference snd_pcm_format_width(format) returns the number of bits per sample. As you have 16 bits per sample and each frame contains only one sample, you should allocate buffer_frames * snd_pcm_format_width(format) / 8 bytes of memory (that 2 from your multiplication represents the number of channels which in your case is 1). Also, I suggest to change your buffer type to char* as it is the only type that is not prone to violating the strict aliasing rule. Thus, the line becomes:
static_cast<char*>(malloc(buffer_frames * (snd_pcm_format_width(format) / 8)));
and when you do the trick to change from short ints to float, the second for loop becomes:
int16_t* sint_buffer = buffer;
for (j = 0; j < buffer_frames; ++j){
float_buffer[j] = (float)sint_buffer[j]/32768.0;
// everything else goes here
}
No matter what what audio file I use, it always cuts off about a fourth of the way through. I have a feeling it's because I'm casting the decoded audio to a Uint8*, but if I don't, the audio plays really fast and it still only plays about half of the file. Also, using SDL_MixAudio instead of SDL_memcpy causes a bunch of copies of the sound to play over each other for some reason.
Uint8* audio_pos;
Uint32 audio_len;
void audioCallback(void* userdata, Uint8* stream, int len) {
if (audio_len == 0) return;
len = (len > audio_len ? audio_len : len);
SDL_memcpy(stream, audio_pos, len);
audio_pos += len;
audio_len -= len;
}
int main(int argc, char *argv[]) {
...
short* decoded;
int channels, rate, len;
len = stb_vorbis_decode_filename(RealPath("music.ogg").c_str(), &channels, &rate, &decoded);
SDL_AudioSpec spec;
spec.freq = rate;
spec.format = AUDIO_S16;
spec.channels = channels;
spec.samples = 2048;
spec.callback = audioCallback;
spec.userdata = NULL;
audio_pos = (Uint8*)decoded;
audio_len = len;
if (SDL_OpenAudio(&spec, NULL) < 0) {
std::cout << "failed to open audio device: " << SDL_GetError() << '\n';
SDL_GL_DeleteContext(context);
SDL_Quit();
return -1;
}
SDL_PauseAudio(0);
SDL_Event windowEvt;
while (true) {
if (audio_len == 0) break;
if (SDL_PollEvent(&windowEvt)) {
if (windowEvt.type == SDL_QUIT) break;
if (windowEvt.type == SDL_KEYUP && windowEvt.key.keysym.sym == SDLK_ESCAPE) break;
}
SDL_GL_SwapWindow(window);
}
...
}
stb_vorbis_decode_filename returns the "number of samples decoded," which is respect to an int16, and doesn't include the channel factor.
You're looking for:
int32 length = stb_vorbis_decode_filename("thing.ogg", &channels, &rate, &decoded);
int32 audio_length = length * channels * (sizeof(int16) / sizeof(uint8));
With respect to SDL_MixAudio vs SDL_memcpy for sound overlap, you need to explicitly clear the stream with a silence value. For example, SDL_memset when you enter the SDL audio callback.
void audioCallback(void* userdata, Uint8* stream, int len)
{
SDL_memset(stream, 0, len);// silence the stream
if (audio_len == 0) return;
len = (len > audio_len ? audio_len : len);
SDL_MixAudio(stream, audio_pos, len, SDL_MIX_MAXVOLUME);// this takes a volume argument
audio_pos += len;
audio_len -= len;
}
The zero passed to SDL_memset should be the same silence value you created in SDL_AudioSpec, when calling SDL_OpenAudioDevice.
I'm developing a project where I need to convert PCM 16-bits 2 channels sound into a IEEE Float 32-bits 2 channels.
To do this I'm using the following code:
void CAudioConverter::ConvI16ToF32(BYTE* pcmFrom, BYTE* floatTo, int length)
{
short* src = reinterpret_cast<short*>(pcmFrom);
float* dst = reinterpret_cast<float*>(floatTo);
for (int n = 0; n < length; n++)
{
dst[n] = static_cast<float>(src[n]) / 32768.0f;
}
}
I have initialized the variable __pcm32_bytesPerFrame with:
WAVEFORMATEX* closestFormat;
ws->default_pb_dev->GetMixFormat(&closestFormat);
__pcm32_bytesPerFrame = closestFormat->nAvgBytesPerSec * (prm->samples_per_frame * 1000 / (prm->clock_rate * closestFormat->nChannels)) / 1000;
strm->pb_max_frame_count is:
hr = ws->default_pb_dev->GetBufferSize(&ws->pb_max_frame_count);
I have a while loop in a dedicated thread the does something like:
hr = strm->default_pb_dev->GetCurrentPadding(&padding);
incoming_frame = __pcm32_bytesPerFrame / 4;
frame_to_render = strm->pb_max_frame_count - padding;
if (frame_to_render >= incoming_frame)
{
frame_to_render = incoming_frame;
} else {
/* Don't get new frame because there's no space */
frame_to_render = 0;
}
if (frame_to_render > 0)
{
pjmedia_frame frame;
hr = strm->pb_client->GetBuffer(frame_to_render, &cur_pb_buf);
if (FAILED(hr)) {
continue;
}
void* destBuffer = (void*)malloc(strm->bytes_per_frame*frame_to_render*sizeof(pj_uint16_t));
if (strm->fmt_id == PJMEDIA_FORMAT_L16) {
/* PCM mode */
frame.type = PJMEDIA_FRAME_TYPE_AUDIO;
frame.size = strm->bytes_per_frame;
frame.timestamp.u64 = strm->pb_timestamp.u64;
frame.bit_info = 0;
frame.buf = destBuffer;
}
status = (*strm->pb_cb)(strm->user_data, &frame);
CAudioConverter* conv = new CAudioConverter();
conv->ConvI16ToF32((BYTE*)destBuffer, cur_pb_buf, frame_to_render);
hr = strm->pb_client->ReleaseBuffer(frame_to_render, 0);
(...)
But, to send the sound to the WASAPI capture buffer I need a BYTE*.
How can I fill my 'floatTo' argument?
Any ideas?
Thanks
What about this:
void CAudioConverter::ConvI16ToF32(BYTE* pcmFrom, BYTE* floatTo, int length)
{
short* src = reinterpret_cast<short*>(pcmFrom);
float* dst = reinterpret_cast<float*>(floatTo);
for (int n = 0; n < length; n++)
{
dst[n] = static_cast<float>(src[n]) / 32768.0f;
}
}
Additionally make sure length indicates the number of elments in pcmFrom and floatTo, and not the number of bytes allocated. In you case pcmFrom should have allocated length*2 bytes and floatTo needs room for length*4 bytes.
The instructions for libjpeg-turbo here describes the TurboJPEG API thus: "This API wraps libjpeg-turbo and provides an easy-to-use interface for compressing and decompressing JPEG images in memory". Great, but are there some solid examples of using this API available? Just looking to decompress a fairly vanilla jpeg in memory.
I've found a few bits such as https://github.com/erlyvideo/jpeg/blob/master/c_src/jpeg.c, which appears to be using the TurboJPEG API, but are there any more solid/varied examples?
The source for libjpeg-turbo is well documented, so that does help.
Ok, I know that you did already solve your problem, but as some people, just like me, could be searching some simple example I will share what I created.
It is an example, compressing and decompressing an RGB image. Otherwise I think that the API documentation of TurboJPEG is quite easy to understand!
Compression:
#include <turbojpeg.h>
const int JPEG_QUALITY = 75;
const int COLOR_COMPONENTS = 3;
int _width = 1920;
int _height = 1080;
long unsigned int _jpegSize = 0;
unsigned char* _compressedImage = NULL; //!< Memory is allocated by tjCompress2 if _jpegSize == 0
unsigned char buffer[_width*_height*COLOR_COMPONENTS]; //!< Contains the uncompressed image
tjhandle _jpegCompressor = tjInitCompress();
tjCompress2(_jpegCompressor, buffer, _width, 0, _height, TJPF_RGB,
&_compressedImage, &_jpegSize, TJSAMP_444, JPEG_QUALITY,
TJFLAG_FASTDCT);
tjDestroy(_jpegCompressor);
//to free the memory allocated by TurboJPEG (either by tjAlloc(),
//or by the Compress/Decompress) after you are done working on it:
tjFree(&_compressedImage);
After that you have the compressed image in _compressedImage.
To decompress you have to do the following:
Decompression:
#include <turbojpeg.h>
long unsigned int _jpegSize; //!< _jpegSize from above
unsigned char* _compressedImage; //!< _compressedImage from above
int jpegSubsamp, width, height;
unsigned char buffer[width*height*COLOR_COMPONENTS]; //!< will contain the decompressed image
tjhandle _jpegDecompressor = tjInitDecompress();
tjDecompressHeader2(_jpegDecompressor, _compressedImage, _jpegSize, &width, &height, &jpegSubsamp);
tjDecompress2(_jpegDecompressor, _compressedImage, _jpegSize, buffer, width, 0/*pitch*/, height, TJPF_RGB, TJFLAG_FASTDCT);
tjDestroy(_jpegDecompressor);
Some random thoughts:
I just came back over this as I am writing my bachelor thesis, and I noticed that if you run the compression in a loop it is preferable to store the biggest size of the JPEG buffer to not have to allocate a new one every turn. Basically, instead of doing:
long unsigned int _jpegSize = 0;
tjCompress2(_jpegCompressor, buffer, _width, 0, _height, TJPF_RGB,
&_compressedImage, &_jpegSize, TJSAMP_444, JPEG_QUALITY,
TJFLAG_FASTDCT);
we would add an object variable, holding the size of the allocated memory long unsigned int _jpegBufferSize = 0; and before every compression round we would set the jpegSize back to that value:
long unsigned int jpegSize = _jpegBufferSize;
tjCompress2(_jpegCompressor, buffer, _width, 0, _height, TJPF_RGB,
&_compressedImage, &jpegSize, TJSAMP_444, JPEG_QUALITY,
TJFLAG_FASTDCT);
_jpegBufferSize = _jpegBufferSize >= jpegSize? _jpegBufferSize : jpegSize;
after the compression one would compare the memory size with the actual jpegSize and set it to the jpegSize if it is higher than the previous memory size.
I ended up using below code as a working example for both JPEG encoding and decoding. Best example that I can find, it's self-contained that initializes a dummy image and output the encoded image to a local file.
Below code is NOT my own, credit goes to https://sourceforge.net/p/libjpeg-turbo/discussion/1086868/thread/e402d36f/#8722 . Posting it here again to help anyone finds it's difficult to get libjpeg turbo working.
#include "turbojpeg.h"
#include <iostream>
#include <string.h>
#include <errno.h>
using namespace std;
int main(void)
{
unsigned char *srcBuf; //passed in as a param containing pixel data in RGB pixel interleaved format
tjhandle handle = tjInitCompress();
if(handle == NULL)
{
const char *err = (const char *) tjGetErrorStr();
cerr << "TJ Error: " << err << " UNABLE TO INIT TJ Compressor Object\n";
return -1;
}
int jpegQual =92;
int width = 128;
int height = 128;
int nbands = 3;
int flags = 0;
unsigned char* jpegBuf = NULL;
int pitch = width * nbands;
int pixelFormat = TJPF_GRAY;
int jpegSubsamp = TJSAMP_GRAY;
if(nbands == 3)
{
pixelFormat = TJPF_RGB;
jpegSubsamp = TJSAMP_411;
}
unsigned long jpegSize = 0;
srcBuf = new unsigned char[width * height * nbands];
for(int j = 0; j < height; j++)
{
for(int i = 0; i < width; i++)
{
srcBuf[(j * width + i) * nbands + 0] = (i) % 256;
srcBuf[(j * width + i) * nbands + 1] = (j) % 256;
srcBuf[(j * width + i) * nbands + 2] = (j + i) % 256;
}
}
int tj_stat = tjCompress2( handle, srcBuf, width, pitch, height,
pixelFormat, &(jpegBuf), &jpegSize, jpegSubsamp, jpegQual, flags);
if(tj_stat != 0)
{
const char *err = (const char *) tjGetErrorStr();
cerr << "TurboJPEG Error: " << err << " UNABLE TO COMPRESS JPEG IMAGE\n";
tjDestroy(handle);
handle = NULL;
return -1;
}
FILE *file = fopen("out.jpg", "wb");
if (!file) {
cerr << "Could not open JPEG file: " << strerror(errno);
return -1;
}
if (fwrite(jpegBuf, jpegSize, 1, file) < 1) {
cerr << "Could not write JPEG file: " << strerror(errno);
return -1;
}
fclose(file);
//write out the compress date to the image file
//cleanup
int tjstat = tjDestroy(handle); //should deallocate data buffer
handle = 0;
}
In the end I used a combination of random code found on the internet (e.g. https://github.com/erlyvideo/jpeg/blob/master/c_src/jpeg.c) and the .c and header files for libjeg-turbo, which are well documented.
This official API is a good information source aswell.
Here's a fragment of code what I use to load jpeg's from memory. Maybe it will require a bit of fixing, because I extracted it from different files in my project. It will load both - grayscale and rgb images (bpp will be set either to 1 or to 3).
struct Image
{
int bpp;
int width;
int height;
unsigned char* data;
};
struct jerror_mgr
{
jpeg_error_mgr base;
jmp_buf jmp;
};
METHODDEF(void) jerror_exit(j_common_ptr jinfo)
{
jerror_mgr* err = (jerror_mgr*)jinfo->err;
longjmp(err->jmp, 1);
}
METHODDEF(void) joutput_message(j_common_ptr)
{
}
bool Image_LoadJpeg(Image* image, unsigned char* img_data, unsigned int img_size)
{
jpeg_decompress_struct jinfo;
jerror_mgr jerr;
jinfo.err = jpeg_std_error(&jerr.base);
jerr.base.error_exit = jerror_exit;
jerr.base.output_message = joutput_message;
jpeg_create_decompress(&jinfo);
image->data = NULL;
if (setjmp(jerr.jmp)) goto bail;
jpeg_mem_src(&jinfo, img_data, img_size);
if (jpeg_read_header(&jinfo, TRUE) != JPEG_HEADER_OK) goto bail;
jinfo.dct_method = JDCT_FLOAT; // change this to JDCT_ISLOW on Android/iOS
if (!jpeg_start_decompress(&jinfo)) goto bail;
if (jinfo.num_components != 1 && jinfo.num_components != 3) goto bail;
image->data = new (std::nothrow) unsigned char [jinfo.output_width * jinfo.output_height * jinfo.output_components];
if (!image->data) goto bail;
{
JSAMPROW ptr = image->data;
while (jinfo.output_scanline < jinfo.output_height)
{
if (jpeg_read_scanlines(&jinfo, &ptr, 1) != 1) goto bail;
ptr += jinfo.output_width * jinfo.output_components;
}
}
if (!jpeg_finish_decompress(&jinfo)) goto bail;
image->bpp = jinfo.output_components;
image->width = jinfo.output_width;
image->height = jinfo.output_height;
jpeg_destroy_decompress(&jinfo);
return true;
bail:
jpeg_destroy_decompress(&jinfo);
if (image->data) delete [] data;
return false;
}