Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

audio: stub

  • Loading branch information...
commit 606ae54a3b5fa4c07eaf206ebb9af39f71cf0b43 1 parent 0177c5f
@indutny authored
View
11 binding.gyp
@@ -1,12 +1,19 @@
{
"targets": [
{
- "target_name": "libopus",
+ "target_name": "vock",
"dependencies": ["deps/opus/opus.gyp:opus"],
"include_dirs": ["src", "deps/opus/opus/include"],
"sources": [
- "src/node_opus.cc"
+ "src/node_opus.cc",
+ "src/audio.cc",
+ "src/vock.cc"
+ ],
+ "conditions": [
+ ["OS=='mac'", {
+ "libraries": [ "-framework AudioToolbox" ],
+ }]
]
}
]
View
4 deps/opus/opus.gyp
@@ -145,7 +145,7 @@
],
},
}],
- ["opus_build_type==\"fixed\"", {
+ ["opus_build_type=='fixed'", {
"defines": ["FIXED_POINT"],
"sources": [
"opus/silk/fixed/LTP_analysis_filter_FIX.c",
@@ -175,7 +175,7 @@
"opus/silk/fixed/schur_FIX",
]
}],
- ["opus_build_type==\"float\"", {
+ ["opus_build_type=='float'", {
"defines": ["FLOATING_POINT"],
"sources": [
"opus/silk/float/apply_sine_window_FLP.c",
View
249 src/audio.cc
@@ -0,0 +1,249 @@
+#include "audio.h"
+#include "common.h"
+
+#include "node.h"
+#include "node_buffer.h"
+
+#include <AudioToolbox/AudioToolbox.h>
+#include <string.h>
+#include <unistd.h>
+
+namespace vock {
+namespace audio {
+
+using namespace node;
+using v8::HandleScope;
+using v8::Handle;
+using v8::Persistent;
+using v8::Local;
+using v8::Array;
+using v8::String;
+using v8::Value;
+using v8::Arguments;
+using v8::Object;
+using v8::Null;
+using v8::True;
+using v8::False;
+using v8::Function;
+using v8::FunctionTemplate;
+using v8::ThrowException;
+
+static Persistent<String> ondata_sym;
+
+Recorder::Recorder(Float64 rate, Float64 seconds) : recording_(false) {
+ OSStatus st;
+
+ // Fill AudioQueue description
+ memset(&desc_, 0, sizeof(desc_));
+ desc_.mSampleRate = rate;
+ desc_.mFormatID = kAudioFormatLinearPCM;
+ desc_.mFormatFlags = CalculateLPCMFlags(16, 16, false, true);
+ desc_.mFramesPerPacket = 1;
+ desc_.mChannelsPerFrame = 1;
+ desc_.mBitsPerChannel = 16;
+ desc_.mBytesPerPacket =
+ desc_.mBytesPerFrame = (desc_.mBitsPerChannel >> 3) *
+ desc_.mChannelsPerFrame;
+ desc_.mReserved = 0;
+
+ // Calculate optimal buffer size
+ bufferSize_ = static_cast<UInt32>(desc_.mSampleRate * desc_.mBytesPerPacket *
+ seconds);
+
+ // Create AudioQueue
+ st = AudioQueueNewInput(&desc_, InputCallback, this, NULL, NULL, 0, &aq_);
+ if (st) {
+ ThrowException(String::New("AudioQueueNewInput failed!"));
+ return;
+ }
+
+ // Create buffers and enqueue them
+ for (int i = 0; i < bufferCount_; i++) {
+ st = AudioQueueAllocateBuffer(aq_, bufferSize_, &buffers_[i]);
+ if (st) {
+ for (int j = 0; j < i; j++) {
+ AudioQueueFreeBuffer(aq_, buffers_[j]);
+ }
+ AudioQueueDispose(aq_, true);
+ ThrowException(String::New("AudioQueueAllocateBuffer failed!"));
+ return;
+ }
+
+ st = AudioQueueEnqueueBuffer(aq_, buffers_[i], 0, NULL);
+ if (st) {
+ for (int j = 0; j <= i; j++) {
+ AudioQueueFreeBuffer(aq_, buffers_[j]);
+ }
+ AudioQueueDispose(aq_, true);
+ ThrowException(String::New("AudioQueueEnqueueBuffer failed!"));
+ return;
+ }
+ }
+
+ // Create async handle
+ if (uv_async_init(uv_default_loop(), &async_, AsyncCallback)) {
+ for (int i = 0; i < bufferCount_; i++) {
+ AudioQueueFreeBuffer(aq_, buffers_[i]);
+ }
+ AudioQueueDispose(aq_, true);
+ ThrowException(String::New("uv_async_init failed!"));
+ return;
+ }
+
+ // Prepare buffer queue
+ if (uv_mutex_init(&queue_mutex_)) abort();
+ memset(queue_, 0, sizeof(queue_));
+ queue_index_ = 0;
+}
+
+
+Recorder::~Recorder() {
+ // TODO: Stop recording?
+ AudioQueueReset(aq_);
+ for (int i = 0; i < bufferCount_; i++) {
+ AudioQueueFreeBuffer(aq_, buffers_[i]);
+ }
+ AudioQueueDispose(aq_, true);
+ uv_close(reinterpret_cast<uv_handle_t*>(&async_), NULL);
+ uv_mutex_destroy(&queue_mutex_);
+}
+
+
+void Recorder::InputCallback(void* data,
+ AudioQueueRef queue,
+ AudioQueueBufferRef buf,
+ const AudioTimeStamp* start_time,
+ UInt32 packet_count,
+ const AudioStreamPacketDescription* packets) {
+ Recorder* r = reinterpret_cast<Recorder*>(data);
+ uv_mutex_lock(&r->queue_mutex_);
+
+ r->queue_[r->queue_index_].buf = buf;
+ r->queue_[r->queue_index_].packet_count = packet_count;
+
+ r->queue_index_++;
+ assert(r->queue_index_ <= r->bufferCount_);
+
+ uv_async_send(&r->async_);
+
+ uv_mutex_unlock(&r->queue_mutex_);
+}
+
+
+void Recorder::AsyncCallback(uv_async_t* async, int status) {
+ HandleScope scope;
+ Recorder* r = container_of(async, Recorder, async_);
+
+ uv_mutex_lock(&r->queue_mutex_);
+ if (r->recording_ == false) {
+ if (r->queue_index_ == r->bufferCount_) {
+ r->Unref();
+ }
+ return;
+ }
+
+ Handle<Array> buffers = Array::New();
+ int j = 0;
+ for (int i = 0; i < r->queue_index_; i++) {
+ if (r->queue_[i].buf == NULL) continue;
+
+ // Pull buffer from queue
+ AudioQueueBufferRef buffer = r->queue_[i].buf;
+ UInt32 packet_count = r->queue_[i].packet_count;
+ r->queue_[i].buf = NULL;
+
+ // Put it in js buffer
+ Buffer* b = Buffer::New(reinterpret_cast<char*>(buffer->mAudioData),
+ packet_count * r->desc_.mBytesPerPacket);
+ buffers->Set(j++, b->handle_);
+
+ // And enqueue again
+ if (AudioQueueEnqueueBuffer(r->aq_, buffer, 0, NULL)) {
+ // XXX: Handle this?!
+ abort();
+ }
+ }
+
+ r->queue_index_ = 0;
+ uv_mutex_unlock(&r->queue_mutex_);
+
+ if (j != 0) {
+ Handle<Value> argv[1] = { buffers };
+
+ if (ondata_sym.IsEmpty()) {
+ ondata_sym = Persistent<String>::New(String::NewSymbol("ondata"));
+ }
+ MakeCallback(r->handle_, ondata_sym, 1, argv);
+ };
+}
+
+
+Handle<Value> Recorder::Start(const Arguments& args) {
+ HandleScope scope;
+ Recorder* r = ObjectWrap::Unwrap<Recorder>(args.This());
+
+ if (r->recording_) return scope.Close(False());
+
+ OSStatus st = AudioQueueStart(r->aq_, NULL);
+ if (st) {
+ return scope.Close(ThrowException(String::New("AudioQueueStart failed!")));
+ }
+ r->recording_ = true;
+ r->Ref();
+
+ return scope.Close(True());
+}
+
+
+Handle<Value> Recorder::Stop(const Arguments& args) {
+ HandleScope scope;
+ Recorder* r = ObjectWrap::Unwrap<Recorder>(args.This());
+
+ if (!r->recording_) return scope.Close(False());
+
+ OSStatus st = AudioQueueStop(r->aq_, NULL);
+ if (st) {
+ return scope.Close(ThrowException(String::New("AudioQueueStop failed!")));
+ }
+ r->recording_ = false;
+ if (r->queue_index_ == r->bufferCount_) {
+ r->Unref();
+ }
+
+ return scope.Close(True());
+}
+
+
+Handle<Value> Recorder::New(const Arguments& args) {
+ HandleScope scope;
+
+ if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsNumber()) {
+ return scope.Close(ThrowException(String::New(
+ "First two arguments should be numbers")));
+ }
+
+ // Second argument is in msec
+ Recorder* r = new Recorder(args[0]->NumberValue(),
+ args[1]->NumberValue() / 1000);
+ r->Wrap(args.Holder());
+
+ return scope.Close(args.This());
+}
+
+
+void Recorder::Init(Handle<Object> target) {
+ HandleScope scope;
+
+ Local<FunctionTemplate> t = FunctionTemplate::New(Recorder::New);
+
+ t->InstanceTemplate()->SetInternalFieldCount(1);
+ t->SetClassName(String::NewSymbol("Recorder"));
+
+ NODE_SET_PROTOTYPE_METHOD(t, "start", Recorder::Start);
+ NODE_SET_PROTOTYPE_METHOD(t, "stop", Recorder::Stop);
+
+ target->Set(String::NewSymbol("Recorder"), t->GetFunction());
+}
+
+} // namespace audio
+} // namespace vock
View
58 src/audio.h
@@ -0,0 +1,58 @@
+#ifndef _SRC_AUDIO_H_
+#define _SRC_AUDIO_H_
+
+#include "node.h"
+#include "node_object_wrap.h"
+
+#include "AudioToolbox/AudioToolbox.h"
+
+namespace vock {
+namespace audio {
+
+using namespace node;
+
+struct QueueItem {
+ AudioQueueBufferRef buf;
+ UInt32 packet_count;
+};
+
+class Recorder : public ObjectWrap {
+ public:
+ Recorder(Float64 rate, Float64 seconds);
+ ~Recorder();
+
+ static void InputCallback(void* data,
+ AudioQueueRef queue,
+ AudioQueueBufferRef buf,
+ const AudioTimeStamp* start_time,
+ UInt32 packet_count,
+ const AudioStreamPacketDescription* packets);
+ static void AsyncCallback(uv_async_t* async, int status);
+
+ static void Init(v8::Handle<v8::Object> target);
+
+ static v8::Handle<v8::Value> New(const v8::Arguments& args);
+ static v8::Handle<v8::Value> Start(const v8::Arguments& args);
+ static v8::Handle<v8::Value> Stop(const v8::Arguments& args);
+
+ protected:
+ static const int bufferCount_ = 16;
+
+ AudioQueueRef aq_;
+ AudioStreamBasicDescription desc_;
+ AudioQueueBufferRef buffers_[bufferCount_];
+ UInt32 bufferSize_;
+
+ // Internal buffer queue
+ QueueItem queue_[bufferCount_];
+ int queue_index_;
+ uv_mutex_t queue_mutex_;
+
+ uv_async_t async_;
+ bool recording_;
+};
+
+} // namespace audio
+} // namespace vock
+
+#endif // _SRC_AUDIO_H_
View
16 src/common.h
@@ -0,0 +1,16 @@
+#ifndef _SRC_COMMON_H_
+#define _SRC_COMMON_H_
+
+#ifndef offset_of
+// g++ in strict mode complains loudly about the system offsetof() macro
+// because it uses NULL as the base address.
+# define offset_of(type, member) \
+ ((intptr_t) ((char *) &(((type *) 8)->member) - 8))
+#endif
+
+#ifndef container_of
+# define container_of(ptr, type, member) \
+ ((type *) ((char *) (ptr) - offset_of(type, member)))
+#endif
+
+#endif // _SRC_COMMON_H_
View
102 src/node_opus.cc
@@ -1,20 +1,43 @@
#include "node_opus.h"
+
#include "node.h"
+#include "node_buffer.h"
#include "node_object_wrap.h"
#include "opus.h"
-#include <stdint.h> // int32_t
-
namespace vock {
namespace opus {
using namespace node;
using namespace v8;
-Opus::Opus(int32_t rate) {
+#define UNWRAP\
+ Opus* o = ObjectWrap::Unwrap<Opus>(args.This());
+
+#define THROW_OPUS_ERROR(err)\
+ ThrowException(String::Concat(String::New("Opus error: "),\
+ String::New(opus_strerror(err))))
+
+Opus::Opus(opus_int32 rate) : rate_(rate), enc_(NULL), dec_(NULL) {
int err;
- enc = opus_encoder_create(rate, 1, OPUS_APPLICATION_VOIP, &err);
- if (err != OPUS_OK) abort();
+
+ enc_ = opus_encoder_create(rate, 1, OPUS_APPLICATION_VOIP, &err);
+ if (err != OPUS_OK) {
+ THROW_OPUS_ERROR(err);
+ return;
+ }
+
+ dec_ = opus_decoder_create(rate, 1, &err);
+ if (err != OPUS_OK) {
+ THROW_OPUS_ERROR(err);
+ return;
+ }
+}
+
+
+Opus::~Opus() {
+ if (enc_ != NULL) opus_encoder_destroy(enc_);
+ if (dec_ != NULL) opus_decoder_destroy(dec_);
}
@@ -32,6 +55,72 @@ Handle<Value> Opus::New(const Arguments& args) {
}
+Handle<Value> Opus::Encode(const Arguments& args) {
+ HandleScope scope;
+
+ UNWRAP
+
+ if (args.Length() < 1 || !Buffer::HasInstance(args[0])) {
+ return scope.Close(ThrowException(String::New(
+ "First argument should be Buffer")));
+ }
+
+ char* data = Buffer::Data(args[0].As<Object>());
+ size_t len = Buffer::Length(args[0].As<Object>());
+
+ if ((len & sizeof(opus_int16)) != 0) {
+ return scope.Close(ThrowException(String::New(
+ "Buffer has incorrect size!")));
+ }
+
+ unsigned char out[4096];
+
+ opus_int32 ret;
+
+ ret = opus_encode(o->enc_,
+ reinterpret_cast<opus_int16*>(data),
+ len / sizeof(opus_int16),
+ out,
+ sizeof(out));
+ if (ret < 0) {
+ return scope.Close(THROW_OPUS_ERROR(ret));
+ }
+
+ return scope.Close(Buffer::New(reinterpret_cast<char*>(out), ret)->handle_);
+}
+
+
+Handle<Value> Opus::Decode(const Arguments& args) {
+ HandleScope scope;
+
+ UNWRAP
+
+ if (args.Length() < 1 || !Buffer::HasInstance(args[0])) {
+ return scope.Close(ThrowException(String::New(
+ "First argument should be Buffer")));
+ }
+
+ char* data = Buffer::Data(args[0].As<Object>());
+ size_t len = Buffer::Length(args[0].As<Object>());
+
+ opus_int16 out[100 * 1024];
+ opus_int16 ret;
+
+ ret = opus_decode(o->dec_,
+ reinterpret_cast<const unsigned char*>(data),
+ len,
+ out,
+ sizeof(out),
+ 1);
+ if (ret < 0) {
+ return scope.Close(THROW_OPUS_ERROR(ret));
+ }
+
+ return scope.Close(Buffer::New(reinterpret_cast<char*>(out),
+ ret * sizeof(out[0]))->handle_);
+}
+
+
void Opus::Init(Handle<Object> target) {
HandleScope scope;
@@ -40,7 +129,8 @@ void Opus::Init(Handle<Object> target) {
t->InstanceTemplate()->SetInternalFieldCount(1);
t->SetClassName(String::NewSymbol("Opus"));
- // NODE_SET_PROTOTYPE_METHOD(t, "init", O::Init);
+ NODE_SET_PROTOTYPE_METHOD(t, "encode", Opus::Encode);
+ NODE_SET_PROTOTYPE_METHOD(t, "decode", Opus::Decode);
target->Set(String::NewSymbol("Opus"), t->GetFunction());
}
View
18 src/node_opus.h
@@ -2,6 +2,7 @@
#define _SRC_NODE_OPUS_H_
#include "node.h"
+#include "v8.h"
#include "node_object_wrap.h"
#include "opus.h"
@@ -9,19 +10,22 @@ namespace vock {
namespace opus {
using namespace node;
-using namespace v8;
class Opus : public ObjectWrap {
public:
- Opus(int32_t rate);
+ Opus(opus_int32 rate);
+ ~Opus();
- static void Init(Handle<Object> target);
+ static void Init(v8::Handle<v8::Object> target);
- static Handle<Value> New(const Arguments& args);
+ static v8::Handle<v8::Value> New(const v8::Arguments& args);
+ static v8::Handle<v8::Value> Encode(const v8::Arguments& args);
+ static v8::Handle<v8::Value> Decode(const v8::Arguments& args);
- private:
- OpusEncoder* enc;
- OpusDecoder* dec;
+ protected:
+ opus_int32 rate_;
+ OpusEncoder* enc_;
+ OpusDecoder* dec_;
};
} // namespace opus
View
17 src/vock.cc
@@ -0,0 +1,17 @@
+#include "audio.h"
+#include "node_opus.h"
+
+#include "node.h"
+
+namespace vock {
+
+using namespace node;
+
+static void Init(v8::Handle<v8::Object> target) {
+ vock::audio::Recorder::Init(target);
+ vock::opus::Opus::Init(target);
+}
+
+NODE_MODULE(vock, Init);
+
+} // namespace vock
Please sign in to comment.
Something went wrong with that request. Please try again.