mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-02 01:44:06 -04:00
added streaming audio support
This commit is contained in:
parent
5547786f00
commit
16b6c060a8
@ -26,6 +26,7 @@
|
||||
userDataAudio.h userDataAudio.I \
|
||||
userDataAudioCursor.h userDataAudioCursor.I \
|
||||
webcamVideo.h webcamVideo.I \
|
||||
microphoneAudio.h microphoneAudio.I \
|
||||
config_movies.h
|
||||
|
||||
#define INCLUDED_SOURCES \
|
||||
@ -44,6 +45,8 @@
|
||||
userDataAudioCursor.cxx \
|
||||
webcamVideo.cxx \
|
||||
webcamVideoDS.cxx \
|
||||
microphoneAudio.cxx \
|
||||
microphoneAudioDS.cxx \
|
||||
config_movies.cxx
|
||||
|
||||
#define INSTALL_HEADERS \
|
||||
@ -59,6 +62,7 @@
|
||||
ffmpegAudioCursor.h ffmpegAudioCursor.I \
|
||||
ffmpegVirtualFile.h ffmpegVirtualFile.I \
|
||||
webcamVideo.h webcamVideo.I \
|
||||
microphoneAudio.h microphoneAudio.I \
|
||||
config_movies.h
|
||||
|
||||
#define IGATESCAN all
|
||||
|
@ -56,6 +56,7 @@ init_libmovies() {
|
||||
UserDataAudio::init_type();
|
||||
UserDataAudioCursor::init_type();
|
||||
WebcamVideo::init_type();
|
||||
MicrophoneAudio::init_type();
|
||||
#ifdef HAVE_FFMPEG
|
||||
FfmpegVideo::init_type();
|
||||
FfmpegVideoCursor::init_type();
|
||||
|
38
panda/src/movies/microphoneAudio.I
Normal file
38
panda/src/movies/microphoneAudio.I
Normal file
@ -0,0 +1,38 @@
|
||||
// Filename: microphoneAudio.I
|
||||
// Created by: jyelon (02Jul07)
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// PANDA 3D SOFTWARE
|
||||
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved
|
||||
//
|
||||
// All use of this software is subject to the terms of the Panda 3d
|
||||
// Software license. You should have received a copy of this license
|
||||
// along with this source code; you will also find a current copy of
|
||||
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
|
||||
//
|
||||
// To contact the maintainers of this program write to
|
||||
// panda3d-general@lists.sourceforge.net .
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MicrophoneAudio::get_channels
|
||||
// Access: Published
|
||||
// Description: Returns the number of channels.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE int MicrophoneAudio::
|
||||
get_channels() const {
|
||||
return _channels;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MicrophoneAudio::get_rate
|
||||
// Access: Published
|
||||
// Description: Returns the sample rate.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE int MicrophoneAudio::
|
||||
get_rate() const {
|
||||
return _rate;
|
||||
}
|
||||
|
90
panda/src/movies/microphoneAudio.cxx
Normal file
90
panda/src/movies/microphoneAudio.cxx
Normal file
@ -0,0 +1,90 @@
|
||||
// Filename: microphoneAudio.cxx
|
||||
// Created by: jyelon (02Jul07)
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// PANDA 3D SOFTWARE
|
||||
// Copyright (c) 2001 - 2007, Disney Enterprises, Inc. All rights reserved
|
||||
//
|
||||
// All use of this software is subject to the terms of the Panda 3d
|
||||
// Software license. You should have received a copy of this license
|
||||
// along with this source code; you will also find a current copy of
|
||||
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
|
||||
//
|
||||
// To contact the maintainers of this program write to
|
||||
// panda3d-general@lists.sourceforge.net
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "microphoneAudio.h"
|
||||
#include "movieAudioCursor.h"
|
||||
|
||||
pvector<PT(MicrophoneAudio)> MicrophoneAudio::_all_microphones;
|
||||
TypeHandle MicrophoneAudio::_type_handle;
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MicrophoneAudio::Destructor
|
||||
// Access: Published, Virtual
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
MicrophoneAudio::
|
||||
~MicrophoneAudio() {
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MicrophoneAudio::find_all_microphones
|
||||
// Access: Public
|
||||
// Description: Scans the hardware for microphones, and pushes them
|
||||
// onto the global list of all microphones.
|
||||
//
|
||||
// There are several implementations of MicrophoneAudio,
|
||||
// including one based on DirectShow, one based on
|
||||
// Linux ALSA, and so forth. These implementations
|
||||
// are contained in one C++ file each, and they export
|
||||
// nothing at all except a single "find_all" function.
|
||||
// Otherwise, they can only be accessed through the
|
||||
// virtual methods of the MicrophoneAudio objects they
|
||||
// create.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void MicrophoneAudio::
|
||||
find_all_microphones() {
|
||||
static bool initialized = false;
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
|
||||
#ifdef HAVE_DIRECTCAM
|
||||
extern void find_all_microphones_ds();
|
||||
find_all_microphones_ds();
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_ALSA
|
||||
extern void find_all_microphones_alsa();
|
||||
find_all_microphones_alsa();
|
||||
#endif
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MicrophoneAudio::get_num_options
|
||||
// Access: Public
|
||||
// Description: Returns the number of microphone options. An "option"
|
||||
// consists of a device plus a set of configuration
|
||||
// parameters. For example, "Soundblaster Audigy Line in
|
||||
// at 44,100 samples/sec" would be an option.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
int MicrophoneAudio::
|
||||
get_num_options() {
|
||||
find_all_microphones();
|
||||
return _all_microphones.size();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MicrophoneAudio::get_option
|
||||
// Access: Public
|
||||
// Description: Returns the nth microphone option.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
PT(MicrophoneAudio) MicrophoneAudio::
|
||||
get_option(int n) {
|
||||
find_all_microphones();
|
||||
nassertr((n >= 0) && (n < (int)_all_microphones.size()), NULL);
|
||||
return _all_microphones[n];
|
||||
}
|
73
panda/src/movies/microphoneAudio.h
Normal file
73
panda/src/movies/microphoneAudio.h
Normal file
@ -0,0 +1,73 @@
|
||||
// Filename: microphoneAudio.h
|
||||
// Created by: jyelon (02Jul07)
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// PANDA 3D SOFTWARE
|
||||
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved
|
||||
//
|
||||
// All use of this software is subject to the terms of the Panda 3d
|
||||
// Software license. You should have received a copy of this license
|
||||
// along with this source code; you will also find a current copy of
|
||||
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
|
||||
//
|
||||
// To contact the maintainers of this program write to
|
||||
// panda3d-general@lists.sourceforge.net .
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef MICROPHONEAUDIO_H
|
||||
#define MICROPHONEAUDIO_H
|
||||
|
||||
#include "movieAudio.h"
|
||||
class MovieAudio;
|
||||
class MovieAudioCursor;
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Class : MicrophoneAudio
|
||||
// Description : Class MicrophoneAudio provides the means to read
|
||||
// raw audio samples from a microphone.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
class EXPCL_PANDA_MOVIES MicrophoneAudio : public MovieAudio {
|
||||
|
||||
PUBLISHED:
|
||||
virtual ~MicrophoneAudio();
|
||||
|
||||
static int get_num_options();
|
||||
static PT(MicrophoneAudio) get_option(int n);
|
||||
|
||||
INLINE int get_channels() const;
|
||||
INLINE int get_rate() const;
|
||||
|
||||
virtual PT(MovieAudioCursor) open() = 0;
|
||||
|
||||
public:
|
||||
static void find_all_microphones();
|
||||
|
||||
protected:
|
||||
int _channels;
|
||||
int _rate;
|
||||
|
||||
static pvector<PT(MicrophoneAudio)> _all_microphones;
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
return _type_handle;
|
||||
}
|
||||
static void init_type() {
|
||||
TypedWritableReferenceCount::init_type();
|
||||
register_type(_type_handle, "MicrophoneAudio",
|
||||
MovieAudio::get_class_type());
|
||||
}
|
||||
virtual TypeHandle get_type() const {
|
||||
return get_class_type();
|
||||
}
|
||||
virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
|
||||
|
||||
private:
|
||||
static TypeHandle _type_handle;
|
||||
};
|
||||
|
||||
#include "microphoneAudio.I"
|
||||
|
||||
#endif
|
422
panda/src/movies/microphoneAudioDS.cxx
Normal file
422
panda/src/movies/microphoneAudioDS.cxx
Normal file
@ -0,0 +1,422 @@
|
||||
// Filename: microphoneAudioDS.cxx
|
||||
// Created by: jyelon (01Nov2007)
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// PANDA 3D SOFTWARE
|
||||
// Copyright (c) 2001 - 2007, Disney Enterprises, Inc. All rights reserved
|
||||
//
|
||||
// All use of this software is subject to the terms of the Panda 3d
|
||||
// Software license. You should have received a copy of this license
|
||||
// along with this source code; you will also find a current copy of
|
||||
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
|
||||
//
|
||||
// To contact the maintainers of this program write to
|
||||
// panda3d-general@lists.sourceforge.net .
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// It goes against Panda3D coding style conventions to hide an
|
||||
// entire class in a C++ file and not expose it through header
|
||||
// files at all. However, in this case, these classes are so full
|
||||
// of OS-specific junk that I feel it is better to hide them
|
||||
// entirely. - Josh
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef HAVE_DIRECTCAM
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
|
||||
#undef Configure
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Class : MicrophoneAudioDS
|
||||
// Description : The directshow implementation of microphones.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
class MicrophoneAudioDS : public MicrophoneAudio
|
||||
{
|
||||
public:
|
||||
static void find_all_microphones_ds();
|
||||
friend void find_all_microphones_ds();
|
||||
|
||||
private:
|
||||
virtual PT(MovieAudioCursor) open();
|
||||
|
||||
int _device_id;
|
||||
int _manufacturer_id;
|
||||
int _product_id;
|
||||
|
||||
struct AudioBuf {
|
||||
HGLOBAL _storage_gh;
|
||||
HGLOBAL _header_gh;
|
||||
LPSTR _storage;
|
||||
LPWAVEHDR _header;
|
||||
};
|
||||
typedef pvector <AudioBuf> AudioBuffers;
|
||||
|
||||
static void delete_buffers(AudioBuffers &buffers);
|
||||
|
||||
friend class MicrophoneAudioCursorDS;
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
return _type_handle;
|
||||
}
|
||||
static void init_type() {
|
||||
MicrophoneAudio::init_type();
|
||||
register_type(_type_handle, "MicrophoneAudioDS",
|
||||
MicrophoneAudio::get_class_type());
|
||||
}
|
||||
virtual TypeHandle get_type() const {
|
||||
return get_class_type();
|
||||
}
|
||||
virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
|
||||
|
||||
private:
|
||||
static TypeHandle _type_handle;
|
||||
};
|
||||
|
||||
TypeHandle MicrophoneAudioDS::_type_handle;
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Class : MicrophoneAudioCursorDS
|
||||
// Description : The directshow implementation of microphones.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
class MicrophoneAudioCursorDS : public MovieAudioCursor
|
||||
{
|
||||
public:
|
||||
typedef MicrophoneAudioDS::AudioBuffers AudioBuffers;
|
||||
MicrophoneAudioCursorDS(MicrophoneAudioDS *src, AudioBuffers &bufs, HWAVEIN hwav);
|
||||
virtual ~MicrophoneAudioCursorDS();
|
||||
|
||||
AudioBuffers _buffers;
|
||||
HWAVEIN _hwavein;
|
||||
int _samples_per_buffer;
|
||||
|
||||
public:
|
||||
virtual void read_samples(int n, PN_int16 *data);
|
||||
virtual int ready() const;
|
||||
|
||||
public:
|
||||
void cleanup();
|
||||
|
||||
HWAVEIN _handle;
|
||||
int _next; // Which buffer is the next one to read from.
|
||||
int _offset; // How many samples to skip in the buffer.
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
return _type_handle;
|
||||
}
|
||||
static void init_type() {
|
||||
MovieAudioCursor::init_type();
|
||||
register_type(_type_handle, "MicrophoneAudioCursorDS",
|
||||
MovieAudioCursor::get_class_type());
|
||||
}
|
||||
virtual TypeHandle get_type() const {
|
||||
return get_class_type();
|
||||
}
|
||||
virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
|
||||
|
||||
private:
|
||||
static TypeHandle _type_handle;
|
||||
};
|
||||
|
||||
TypeHandle MicrophoneAudioCursorDS::_type_handle;
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MicrophoneAudioDS::find_all_microphones_ds
|
||||
// Access: Public, Static
|
||||
// Description: Finds all DirectShow microphones and adds them to
|
||||
// the global list _all_microphones.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void MicrophoneAudioDS::
|
||||
find_all_microphones_ds() {
|
||||
MMRESULT stat;
|
||||
static int freqs[] = { 11025, 22050, 44100, 48000, 0 };
|
||||
|
||||
int ndevs = waveInGetNumDevs();
|
||||
for (int i=0; i<ndevs; i++) {
|
||||
WAVEINCAPS caps;
|
||||
stat = waveInGetDevCaps(i, &caps, sizeof(caps));
|
||||
if (stat != MMSYSERR_NOERROR) continue;
|
||||
for (int chan=1; chan<=2; chan++) {
|
||||
for (int fselect=0; freqs[fselect]; fselect++) {
|
||||
WAVEFORMATEX format;
|
||||
int freq = freqs[fselect];
|
||||
format.wFormatTag = WAVE_FORMAT_PCM;
|
||||
format.nChannels = chan;
|
||||
format.nSamplesPerSec = freq;
|
||||
format.nAvgBytesPerSec = freq * chan * 2;
|
||||
format.nBlockAlign = 2 * chan;
|
||||
format.wBitsPerSample = 16;
|
||||
format.cbSize = 0;
|
||||
stat = waveInOpen(NULL, i, &format, NULL, NULL, WAVE_FORMAT_QUERY);
|
||||
if (stat == MMSYSERR_NOERROR) {
|
||||
PT(MicrophoneAudioDS) p = new MicrophoneAudioDS();
|
||||
ostringstream name;
|
||||
name << "WaveIn: " << caps.szPname << " Chan:" << chan << " HZ:" << freq;
|
||||
p->set_name(name.str());
|
||||
p->_device_id = i;
|
||||
p->_manufacturer_id = caps.wMid;
|
||||
p->_product_id = caps.wPid;
|
||||
p->_rate = freq;
|
||||
p->_channels = chan;
|
||||
_all_microphones.push_back((MicrophoneAudioDS*)p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void find_all_microphones_ds() {
|
||||
MicrophoneAudioDS::init_type();
|
||||
// MicrophoneAudioCursorDS::init_type();
|
||||
MicrophoneAudioDS::find_all_microphones_ds();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MicrophoneAudioDS::delete_buffers
|
||||
// Access: Private, Static
|
||||
// Description: Delete a set of audio buffers.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void MicrophoneAudioDS::
|
||||
delete_buffers(AudioBuffers &buffers) {
|
||||
for (int i=0; i<(int)buffers.size(); i++) {
|
||||
AudioBuf &buf = buffers[i];
|
||||
if (buf._header_gh) {
|
||||
GlobalUnlock(buf._header_gh);
|
||||
GlobalFree(buf._header_gh);
|
||||
}
|
||||
if (buf._storage_gh) {
|
||||
GlobalUnlock(buf._storage_gh);
|
||||
GlobalFree(buf._storage_gh);
|
||||
}
|
||||
}
|
||||
buffers.clear();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MicrophoneAudioDS::open
|
||||
// Access: Published, Virtual
|
||||
// Description: Open this video, returning a MovieVideoCursor.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
PT(MovieAudioCursor) MicrophoneAudioDS::
|
||||
open() {
|
||||
|
||||
// Allocate the buffers. 64 buffers, not quite 1/20 sec each.
|
||||
int samples;
|
||||
switch (_rate) {
|
||||
case 11025: samples=512; break;
|
||||
case 22050: samples=1024; break;
|
||||
case 44100: samples=2048; break;
|
||||
}
|
||||
int bytes = _channels * samples * 2;
|
||||
|
||||
bool failed = false;
|
||||
AudioBuffers buffers;
|
||||
for (int i=0; i<64; i++) {
|
||||
AudioBuf buf;
|
||||
buf._storage_gh = 0;
|
||||
buf._header_gh = 0;
|
||||
buf._storage = 0;
|
||||
buf._header = 0;
|
||||
|
||||
buf._storage_gh = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, bytes);
|
||||
buf._header_gh = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, sizeof(WAVEHDR));
|
||||
if (buf._storage_gh != 0) {
|
||||
buf._storage = (LPSTR)GlobalLock(buf._storage_gh);
|
||||
}
|
||||
if (buf._header_gh != 0) {
|
||||
buf._header = (LPWAVEHDR)GlobalLock(buf._header_gh);
|
||||
}
|
||||
if (buf._storage && buf._header) {
|
||||
ZeroMemory(buf._header, sizeof(WAVEHDR));
|
||||
buf._header->lpData = buf._storage;
|
||||
buf._header->dwBufferLength = bytes;
|
||||
} else {
|
||||
failed = true;
|
||||
}
|
||||
buffers.push_back(buf);
|
||||
if (failed) break;
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
delete_buffers(buffers);
|
||||
nassert_raise("Could not allocate audio input buffers.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
WAVEFORMATEX format;
|
||||
format.wFormatTag = WAVE_FORMAT_PCM;
|
||||
format.nChannels = _channels;
|
||||
format.nSamplesPerSec = _rate;
|
||||
format.nAvgBytesPerSec = _rate * _channels * 2;
|
||||
format.nBlockAlign = 2 * _channels;
|
||||
format.wBitsPerSample = 16;
|
||||
format.cbSize = 0;
|
||||
|
||||
HWAVEIN hwav;
|
||||
MMRESULT stat = waveInOpen(&hwav, _device_id, &format, NULL, NULL, CALLBACK_NULL);
|
||||
|
||||
if (stat != MMSYSERR_NOERROR) {
|
||||
delete_buffers(buffers);
|
||||
nassert_raise("Could not open audio input device.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (int i=0; i<(int)buffers.size(); i++) {
|
||||
stat = waveInPrepareHeader(hwav, buffers[i]._header, sizeof(WAVEHDR));
|
||||
if (stat == MMSYSERR_NOERROR) {
|
||||
stat = waveInAddBuffer(hwav, buffers[i]._header, sizeof(WAVEHDR));
|
||||
}
|
||||
if (stat != MMSYSERR_NOERROR) {
|
||||
waveInClose(hwav);
|
||||
delete_buffers(buffers);
|
||||
nassert_raise("Could not queue buffers for audio input device.");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
stat = waveInStart(hwav);
|
||||
if (stat != MMSYSERR_NOERROR) {
|
||||
waveInClose(hwav);
|
||||
delete_buffers(buffers);
|
||||
nassert_raise("Could not start recording on input device.");
|
||||
return NULL;
|
||||
}
|
||||
return new MicrophoneAudioCursorDS(this, buffers, hwav);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MicrophoneAudioCursorDS::Constructor
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
MicrophoneAudioCursorDS::
|
||||
MicrophoneAudioCursorDS(MicrophoneAudioDS *src, AudioBuffers &bufs, HWAVEIN hwav) :
|
||||
MovieAudioCursor(src),
|
||||
_buffers(bufs),
|
||||
_handle(hwav),
|
||||
_next(0),
|
||||
_offset(0)
|
||||
{
|
||||
_audio_rate = src->get_rate();
|
||||
_audio_channels = src->get_channels();
|
||||
_length = 1.0E10;
|
||||
_can_seek = false;
|
||||
_can_seek_fast = false;
|
||||
_aborted = false;
|
||||
_samples_per_buffer = bufs[0]._header->dwBufferLength / (2 * _audio_channels);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MicrophoneAudioCursorDS::cleanup
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void MicrophoneAudioCursorDS::
|
||||
cleanup() {
|
||||
if (_handle) {
|
||||
waveInClose(_handle);
|
||||
_handle = 0;
|
||||
}
|
||||
MicrophoneAudioDS::delete_buffers(_buffers);
|
||||
_next = 0;
|
||||
_offset = 0;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MicrophoneAudioCursorDS::Destructor
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
MicrophoneAudioCursorDS::
|
||||
~MicrophoneAudioCursorDS() {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MicrophoneAudioCursorDS::fetch_into_buffer
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void MicrophoneAudioCursorDS::
|
||||
read_samples(int n, PN_int16 *data) {
|
||||
int orign = n;
|
||||
if (_handle) {
|
||||
while (1) {
|
||||
int index = _next % _buffers.size();
|
||||
if ((_buffers[index]._header->dwFlags & WHDR_DONE)==0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Find start of data in buffer.
|
||||
PN_int16 *src = (PN_int16*)(_buffers[index]._storage);
|
||||
src += (_offset * _audio_channels);
|
||||
|
||||
// Decide how many samples to extract from this buffer.
|
||||
int samples = _samples_per_buffer;
|
||||
samples -= _offset;
|
||||
if (samples > n) samples = n;
|
||||
|
||||
// Copy data to output buffer.
|
||||
memcpy(data, src, samples * 2 * _audio_channels);
|
||||
|
||||
// Advance pointers.
|
||||
data += samples * _audio_channels;
|
||||
n -= samples;
|
||||
_offset += samples;
|
||||
_samples_read += samples;
|
||||
if (_offset != _samples_per_buffer) {
|
||||
break;
|
||||
}
|
||||
_buffers[index]._header->dwFlags &= ~(WHDR_DONE);
|
||||
MMRESULT stat = waveInUnprepareHeader(_handle, _buffers[index]._header, sizeof(WAVEHDR));
|
||||
if (stat == MMSYSERR_NOERROR) {
|
||||
stat = waveInPrepareHeader(_handle, _buffers[index]._header, sizeof(WAVEHDR));
|
||||
}
|
||||
if (stat == MMSYSERR_NOERROR) {
|
||||
stat = waveInAddBuffer(_handle, _buffers[index]._header, sizeof(WAVEHDR));
|
||||
}
|
||||
if (stat != MMSYSERR_NOERROR) {
|
||||
movies_cat.error() << "Could not requeue audio buffers, closing microphone.\n";
|
||||
cleanup();
|
||||
break;
|
||||
}
|
||||
_next += 1;
|
||||
_offset = 0;
|
||||
}
|
||||
}
|
||||
if (n > 0) {
|
||||
memcpy(data, 0, n*2*_audio_channels);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MicrophoneAudioCursorDS::ready
|
||||
// Access: Published
|
||||
// Description:
|
||||
////////////////////////////////////////////////////////////////////
|
||||
int MicrophoneAudioCursorDS::
|
||||
ready() const {
|
||||
if (_handle == 0) return 0;
|
||||
int total = 0;
|
||||
for (int i=0; i<(int)_buffers.size(); i++) {
|
||||
int index = (_next + i) % (_buffers.size());
|
||||
if ((_buffers[index]._header->dwFlags & WHDR_DONE)==0) {
|
||||
break;
|
||||
}
|
||||
total += _samples_per_buffer;
|
||||
}
|
||||
total -= _offset;
|
||||
return total;
|
||||
}
|
||||
|
||||
|
||||
#endif // HAVE_DIRECTSHOW
|
@ -74,40 +74,6 @@ length() const {
|
||||
return _length;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MovieAudioCursor::ready
|
||||
// Access: Public
|
||||
// Description: Returns the number of audio samples that are ready
|
||||
// to read. This is primarily relevant for sources like
|
||||
// microphones which produce samples at a fixed rate.
|
||||
// If you try to read more samples than are ready, the
|
||||
// result will be silent samples.
|
||||
//
|
||||
// Some audio streams do not have a limit on how fast
|
||||
// they can produce samples. Such streams will always
|
||||
// return 0x40000000 as the ready-count. This may well
|
||||
// exceed the length of the audio stream. You therefore
|
||||
// need to check length separately.
|
||||
//
|
||||
// If the aborted flag is set, that means the ready count
|
||||
// is no longer being replenished. For example, a
|
||||
// MovieAudioCursor might be reading from an internet
|
||||
// radio station, and it might buffer data to avoid
|
||||
// underruns. If it loses connection to the radio
|
||||
// station, it will set the aborted flag to indicate that
|
||||
// the buffer is no longer being replenished. But it is
|
||||
// still ok to read the samples that are in the buffer,
|
||||
// at least until they run out. Once those are gone,
|
||||
// there will be no more.
|
||||
//
|
||||
// An audio consumer needs to check the length, the
|
||||
// ready status, and the aborted flag.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE int MovieAudioCursor::
|
||||
ready() const {
|
||||
return _ready;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MovieAudioCursor::can_seek
|
||||
// Access: Public
|
||||
@ -165,6 +131,6 @@ tell() const {
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE void MovieAudioCursor::
|
||||
skip_samples(int n) {
|
||||
read_samples(n, 0);
|
||||
read_samples(n, (PN_int16*)0);
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,6 @@ MovieAudioCursor(MovieAudio *src) :
|
||||
_length(1.0E10),
|
||||
_can_seek(true),
|
||||
_can_seek_fast(true),
|
||||
_ready(0x40000000),
|
||||
_aborted(false),
|
||||
_samples_read(0)
|
||||
{
|
||||
@ -69,12 +68,69 @@ read_samples(int n, PN_int16 *data) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i=0; i<n; i++) {
|
||||
int desired = n * _audio_channels;
|
||||
for (int i=0; i<desired; i++) {
|
||||
data[i] = 0;
|
||||
}
|
||||
_samples_read += n;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MovieAudioCursor::read_samples
|
||||
// Access: Published
|
||||
// Description: Read audio samples from the stream into a
|
||||
// Datagram. N is the number of samples you wish
|
||||
// to read. Multiple-channel audio will be interleaved.
|
||||
//
|
||||
// This is not particularly efficient, but it may be
|
||||
// a convenient way to manipulate samples in python.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void MovieAudioCursor::
|
||||
read_samples(int n, Datagram *dg) {
|
||||
PN_int16 tmp[4096];
|
||||
while (n > 0) {
|
||||
int blocksize = (4096 / _audio_channels);
|
||||
if (blocksize > n) blocksize = n;
|
||||
int words = blocksize * _audio_channels;
|
||||
read_samples(blocksize, tmp);
|
||||
for (int i=0; i<words; i++) {
|
||||
dg->add_int16(tmp[i]);
|
||||
}
|
||||
n -= blocksize;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MovieAudioCursor::read_samples
|
||||
// Access: Published
|
||||
// Description: Read audio samples from the stream and returns
|
||||
// them as a string. The samples are stored little-endian
|
||||
// in the string. N is the number of samples you wish
|
||||
// to read. Multiple-channel audio will be interleaved.
|
||||
//
|
||||
// This is not particularly efficient, but it may be
|
||||
// a convenient way to manipulate samples in python.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
string MovieAudioCursor::
|
||||
read_samples(int n) {
|
||||
ostringstream result;
|
||||
PN_int16 tmp[4096];
|
||||
while (n > 0) {
|
||||
int blocksize = (4096 / _audio_channels);
|
||||
if (blocksize > n) blocksize = n;
|
||||
int words = blocksize * _audio_channels;
|
||||
read_samples(blocksize, tmp);
|
||||
for (int i=0; i<words; i++) {
|
||||
PN_int16 word = tmp[i];
|
||||
result.put((char)(word & 255));
|
||||
result.put((char)((word>>8) & 255));
|
||||
}
|
||||
n -= blocksize;
|
||||
}
|
||||
return result.str();
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MovieAudioCursor::seek
|
||||
// Access: Published, Virtual
|
||||
@ -103,3 +159,37 @@ seek(double offset) {
|
||||
_samples_read = 0;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: MovieAudioCursor::ready
|
||||
// Access: Public
|
||||
// Description: Returns the number of audio samples that are ready
|
||||
// to read. This is primarily relevant for sources like
|
||||
// microphones which produce samples at a fixed rate.
|
||||
// If you try to read more samples than are ready, the
|
||||
// result will be silent samples.
|
||||
//
|
||||
// Some audio streams do not have a limit on how fast
|
||||
// they can produce samples. Such streams will always
|
||||
// return 0x40000000 as the ready-count. This may well
|
||||
// exceed the length of the audio stream. You therefore
|
||||
// need to check length separately.
|
||||
//
|
||||
// If the aborted flag is set, that means the ready count
|
||||
// is no longer being replenished. For example, a
|
||||
// MovieAudioCursor might be reading from an internet
|
||||
// radio station, and it might buffer data to avoid
|
||||
// underruns. If it loses connection to the radio
|
||||
// station, it will set the aborted flag to indicate that
|
||||
// the buffer is no longer being replenished. But it is
|
||||
// still ok to read the samples that are in the buffer,
|
||||
// at least until they run out. Once those are gone,
|
||||
// there will be no more.
|
||||
//
|
||||
// An audio consumer needs to check the length, the
|
||||
// ready status, and the aborted flag.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
int MovieAudioCursor::
|
||||
ready() const {
|
||||
return 0x40000000;
|
||||
}
|
||||
|
||||
|
@ -50,20 +50,21 @@ PUBLISHED:
|
||||
INLINE double length() const;
|
||||
INLINE bool can_seek() const;
|
||||
INLINE bool can_seek_fast() const;
|
||||
INLINE bool aborted() const;
|
||||
INLINE double tell() const;
|
||||
INLINE void skip_samples(int n);
|
||||
INLINE int ready() const;
|
||||
INLINE bool aborted() const;
|
||||
virtual int ready() const;
|
||||
virtual void seek(double offset);
|
||||
void read_samples(int n, Datagram *dg);
|
||||
string read_samples(int n);
|
||||
|
||||
public:
|
||||
virtual void read_samples(int n, PN_int16 *data);
|
||||
|
||||
|
||||
protected:
|
||||
PT(MovieAudio) _source;
|
||||
int _audio_rate;
|
||||
int _audio_channels;
|
||||
int _ready;
|
||||
double _length;
|
||||
bool _can_seek;
|
||||
bool _can_seek_fast;
|
||||
|
@ -21,4 +21,7 @@
|
||||
#include "webcamVideo.cxx"
|
||||
#include "webcamVideoDS.cxx"
|
||||
|
||||
#include "microphoneAudio.cxx"
|
||||
#include "microphoneAudioDS.cxx"
|
||||
|
||||
#include "config_movies.cxx"
|
||||
|
@ -46,19 +46,6 @@ UserDataAudio::
|
||||
~UserDataAudio() {
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: UserDataAudio::update_cursor
|
||||
// Access: Private
|
||||
// Description: Make sure that the UserDataAudioCursor's ready
|
||||
// and aborted status flags are correct.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void UserDataAudio::
|
||||
update_cursor() {
|
||||
if (_cursor == 0) return;
|
||||
_cursor->_ready = _data.size();
|
||||
_cursor->_aborted = _aborted;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: UserDataAudio::open
|
||||
// Access: Published, Virtual
|
||||
@ -73,7 +60,6 @@ open() {
|
||||
return NULL;
|
||||
}
|
||||
_cursor = new UserDataAudioCursor(this);
|
||||
update_cursor();
|
||||
return _cursor;
|
||||
}
|
||||
|
||||
@ -87,48 +73,73 @@ open() {
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void UserDataAudio::
|
||||
read_samples(int n, PN_int16 *data) {
|
||||
int nread = n;
|
||||
if (nread > (int)_data.size()) {
|
||||
nread = _data.size();
|
||||
}
|
||||
for (int i=0; i<nread; i++) {
|
||||
int ready = (_data.size() / _desired_channels);
|
||||
int desired = n * _desired_channels;
|
||||
int avail = ready * _desired_channels;
|
||||
if (avail > desired) avail = desired;
|
||||
for (int i=0; i<avail; i++) {
|
||||
data[i] = _data[i];
|
||||
}
|
||||
for (int i=nread; i<n; i++) {
|
||||
for (int i=avail; i<desired; i++) {
|
||||
data[i] = 0;
|
||||
}
|
||||
for (int i=0; i<nread; i++) {
|
||||
for (int i=0; i<avail; i++) {
|
||||
_data.pop_front();
|
||||
}
|
||||
update_cursor();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: UserDataAudio::append
|
||||
// Access: Published
|
||||
// Access: Public
|
||||
// Description: Appends audio samples to the buffer.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void UserDataAudio::
|
||||
append(PN_int16 *data, int len) {
|
||||
append(PN_int16 *data, int n) {
|
||||
nassertv(!_aborted);
|
||||
for (int i=0; i<len; i++) {
|
||||
int words = n * _desired_channels;
|
||||
for (int i=0; i<words; i++) {
|
||||
_data.push_back(data[i]);
|
||||
}
|
||||
update_cursor();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: UserDataAudio::append
|
||||
// Access: Published
|
||||
// Description: Appends audio samples to the buffer.
|
||||
// Description: Appends audio samples to the buffer from a
|
||||
// datagram. This is intended to make it easy to
|
||||
// send streaming raw audio over a network.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void UserDataAudio::
|
||||
append(int val) {
|
||||
append(DatagramIterator *src, int n) {
|
||||
nassertv(!_aborted);
|
||||
PN_int16 truncated = (PN_int16)val;
|
||||
nassertv(truncated == val);
|
||||
_data.push_back(truncated);
|
||||
update_cursor();
|
||||
int maxlen = src->get_remaining_size() / (2 * _desired_channels);
|
||||
if (n > maxlen) n = maxlen;
|
||||
int words = n * _desired_channels;
|
||||
for (int i=0; i<words; i++) {
|
||||
_data.push_back(src->get_int16());
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: UserDataAudio::append
|
||||
// Access: Published
|
||||
// Description: Appends audio samples to the buffer from a
|
||||
// string. The samples must be stored little-endian
|
||||
// in the string. This is not particularly efficient,
|
||||
// but it may be convenient to deal with samples in
|
||||
// python.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void UserDataAudio::
|
||||
append(const string &str) {
|
||||
nassertv(!_aborted);
|
||||
int samples = str.size() / (2 * _desired_channels);
|
||||
int words = samples * _desired_channels;
|
||||
for (int i=0; i<words; i++) {
|
||||
int c1 = ((unsigned char)str[i*2+0]);
|
||||
int c2 = ((unsigned char)str[i*2+1]);
|
||||
PN_int16 n = (c1 | (c2 << 8));
|
||||
_data.push_back(n);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
@ -140,5 +151,4 @@ append(int val) {
|
||||
void UserDataAudio::
|
||||
done() {
|
||||
_aborted = true;
|
||||
update_cursor();
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
#define USERDATAAUDIO_H
|
||||
|
||||
#include "movieAudio.h"
|
||||
#include "datagramIterator.h"
|
||||
class MovieAudioCursor;
|
||||
class UserDataAudioCursor;
|
||||
|
||||
@ -35,8 +36,9 @@ class EXPCL_PANDA_MOVIES UserDataAudio : public MovieAudio {
|
||||
virtual ~UserDataAudio();
|
||||
virtual PT(MovieAudioCursor) open();
|
||||
|
||||
void append(PN_int16 *data, int len);
|
||||
void append(int value); // Not fast enough, but useful for debugging.
|
||||
void append(PN_int16 *data, int n);
|
||||
void append(DatagramIterator *src, int len=0x40000000);
|
||||
void append(const string &str);
|
||||
void done(); // A promise not to write any more samples.
|
||||
|
||||
private:
|
||||
|
@ -33,7 +33,6 @@ UserDataAudioCursor(UserDataAudio *src) :
|
||||
_audio_channels = src->_desired_channels;
|
||||
_can_seek = false;
|
||||
_can_seek_fast = false;
|
||||
_ready = 0;
|
||||
_aborted = false;
|
||||
}
|
||||
|
||||
@ -60,4 +59,18 @@ void UserDataAudioCursor::
|
||||
read_samples(int n, PN_int16 *data) {
|
||||
UserDataAudio *source = (UserDataAudio*)(MovieAudio*)_source;
|
||||
source->read_samples(n, data);
|
||||
_samples_read += n;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: UserDataAudioCursor::ready
|
||||
// Access: Private
|
||||
// Description: Returns the number of audio samples ready to be
|
||||
// read.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
int UserDataAudioCursor::
|
||||
ready() const {
|
||||
UserDataAudio *source = (UserDataAudio*)(MovieAudio*)_source;
|
||||
((UserDataAudioCursor*)this)->_aborted = source->_aborted;
|
||||
return (source->_data.size()) / _audio_channels;
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ PUBLISHED:
|
||||
|
||||
public:
|
||||
virtual void read_samples(int n, PN_int16 *data);
|
||||
virtual int ready() const;
|
||||
friend class UserDataAudio;
|
||||
|
||||
public:
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Filename: webcamVideoDX.cxx
|
||||
// Filename: webcamVideoDS.cxx
|
||||
// Created by: jyelon (01Nov2007)
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
@ -102,7 +102,7 @@ public:
|
||||
return _type_handle;
|
||||
}
|
||||
static void init_type() {
|
||||
MovieVideo::init_type();
|
||||
WebcamVideo::init_type();
|
||||
register_type(_type_handle, "WebcamVideoDS",
|
||||
WebcamVideo::get_class_type());
|
||||
}
|
||||
@ -295,12 +295,6 @@ get_moniker_name(IMoniker *pMoniker) {
|
||||
return res;
|
||||
}
|
||||
|
||||
struct DeviceInfo {
|
||||
string _name;
|
||||
IMoniker *_moniker;
|
||||
AM_MEDIA_TYPE *_media;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: WebcamVideoDS::add_device
|
||||
// Access: Public, Static
|
||||
|
Loading…
x
Reference in New Issue
Block a user