|
Hi Ruslan Ciurca, thanks for a neat little program. Works like a charm. Is it possible to record from the windows default audio device instead of taking the input from the user? This is useful in recording from most Instant Messengers and VOIP tools.
So, instead of -device=XXX and -line=yyy, could we just record form the default soundcard?
Thanks in advance
Nitin
|
|
|
|
|
Yep, possible. I have received two similar questions on this forum and answered them, but ... it seems that codeproject is doing some re-works and i can't access those posts. In any case:
1. if the sound-mixer is one, then you know the default one.
2. if more sound-mixers are installed, then you need to check registry settings for the default. Check:
HKU,"\.Default\Software\Microsoft\Multimedia\Sound Mapper"
or
HKCU,"\Software\Microsoft\Multimedia\Sound Mapper"
Regards,
Ruslan
|
|
|
|
|
Hi Ruslan,
Again, thanks for your reply! I did search through the forum before I asked, but apologies if I should have found it somewhere else.
Three questions - Two technical and one general:
a) By reading Playback or Record key from Registry, I will get the name of the sound card. Is there a safe mechanism to select the "line" from the sound card device that will record the audio stream (Wave Out). I know you said "Almost every sound card supports Wave Out Mix or Stereo Mix", so is it safe, in your experience, to assume "Stereo Mix" as the default line for the device?
b) Do you know if the registry key method is Vista ready? I dont have a Vista box to test on, but I will pretty soon.
c) This is the general question - I have adapted your code and tied it to Skype API to create a Skype VOIP recorder. I am planning to create an open source project on Google. First, is it ok to give you credit and have your code as part of the open source project? Second, since I am not a C++ programmer, I have done mostly a hack job by looking at code from multiple places for the recording and C++ ATL/COM code. So while my code works and my Skype API knowledge is upto scracth and the exe has been thoroughly tested by a team of testers it definitely needs someone who knows C++ to lead the project. Since your code is the most critical component of this recorder, I would like to invite you to take the lead on this project, if you want to!
I personally think a lot of people will benefit by this open source VOIP recorder. Sorry for the longish post! Please do let me know what you think.
Thanks
Nitin
|
|
|
|
|
Hi Nitin,
No problems ... back to your questions:
(a) - Most of the soundcards do have those ('Wave Out Mix' or 'Stereo Mix') lines, but ... it is not safe to use them. Unfortunately, this is absolutely true with regards to Windows Vista. I didn't use Vista hardly, but i have mentioned that sounding 'stuff' is completely different in Vista comparing with other Windows versions. However, it is safe to use "Microphone" as the default line.
(b) - Most probably not, but i haven't tested this as well.
>>(c) - "First, is it ok to give you credit and have your code as part of the open source project?"
Yep, that is absolutely fine.
>>(c) - "I would like to invite you to take the lead on this project, if you want to!"
Do you mean lead or help? ... With "help" and "code adjust/bug fixing" yes, i am ok. By the way, have you thought about doing all this stuff in Java (will be good for Linux/Mac OS users as well)?
Regards,
Ruslan
|
|
|
|
|
Hi Ruslan,
Again, thanks for your replies!
<ruslan>However, it is safe to use "Microphone" as the default line.
That will not work because we need both the incoming sound and the outgoing sound from most VOIP tools. So, I made a list of all the Wave Out mixer devices I could find, from your article and google i.e. "Stereo Mix", "Wave Out Mix", "Mono Mix", "What U Hear", "Wave".
Please let me know if you know of any more.
For Vista, I am not sure what will work. This (solution [^]seems work for most people! But, since there are commercial products in the market that do not require this work around I think there should be some way to record from the soundcard in Vista as well
As for the open source project, I meant lead and not contribute. I will dedicate resources from my organization for testing and the Skype integration. But this really need someone to take the lead for C++ standards, unicode support, logging framework etc for the project
I do not mind doing it in Java, in fact that would reduce my headaches a lot! But, the Skype Java API typically lags the COM API. I will take a look at Javasound and Java Media Framework once this solution is stable.
The thing I like most about the VC++ version is
a) You did a lot of the work
b) The footprint is amazingly small
Thanks
Nitin
|
|
|
|
|
Hi Nitin,
Yes, having a predefined list of 'Wave Out Mix' lines sounds like a good approach. Unfortunately i don't know other names for such lines.
For Vista, i don't see other approaches, at the moment, other than testing & investigating.
Regarding leading; the list of projects, i am currently involved in, doesn't allow me to take the risk of leading another one. And, by the way, this is a reason why can't finish (yet) the last article from the "MP3 broadcasting" series. So, i better say "no" for the leading and "yes" for contribution, at least in this scenario i am confident that i can keep my promises.
Regarding Java; i have succeeded to do the same (what is covered in this article) using Java. By the way, starting with J2SE 5.0, Java sound is part of the standard. So, with J2SE and Tritonus (open source Java wrapper of the LAME API) it costs nothing to implement similar functionality with Java. Java Media Framework is useless when MP3 encoding is required. I am not familiar with Skype API, but if Java version isn't 'good enough' then it should be easy to implement a wrapper to the standard API with JNI.
Regards,
Ruslan
|
|
|
|
|
Hi Ruslan,
Sorry for the absence, work & travel called
Ok, I think the project is ready to be released to open source though its not as standard in code base etc as I want it to be, but then there is no end to how much better it can be made before releasing it.
Any thoughts about license? I was thinking Apache License 2.0 or Mozilla Public License. Do you have any specific OSI license in mind? The more open the better, what do you think?
As for Java, If you share the code for Tritonous, I will code the Skype aspects pretty quick. We can leave the choice to people about which one they use.
Also, if you have any specific credits etc that you want in the header of your source code, could you please email it to me at nitin dot shenoy at gmail dot com?
I will be putting in my company name in the code that I have written unless you have objections.
Thanks
Nitin
|
|
|
|
|
I'd like to know how I could
record from device GN 2100 USB LINE Mikrofon on (for example) left channel
and record voice stream (VOIP) or Headset speaker on right channel in mp3-file.
I think it's possible.
But I need a hint (or help) how i could solve this problem.
But may be my way is wrong.
Thanks.
T-O
|
|
|
|
|
So, you want to mix sounds from two different sources, something like you hear yourself and what others are speaking.
I am not sure if using right/left channels idea will work fine, but have a try. In any case, when recording 16 bits PCM from any WaiveIn, it should be easy to take 8 bits (from 16) of the one of the channels (left or right). Then compute 8 bits of the left channel from one source and 8 bits of the right channel from other source in order to obtain a mixed 16 bits sample again (8bitsLeft*16 + 8bitsRight = 16 bits mixed PCM). As an input to MP3 encoder you will pass a buffer of such 16 bits mixed samples.
Alternatively, you can try recoding 8 bits PCM from both sources and mix those bits in the same way as explained above.
However, if driver(s) allow that, I would relay on hardware mixing if possible (redirect both WaveIn sources to a WaveOut device which allows recording).
Another solution (in case hardware mixing is not supported/possible) would be to compute waves (a bit of math) of the 16 bits PCM from both sources into another wave (16 bits as well). I haven't checked the google yet, but I am sure you should find few good, ready to use classes, to compute two waves.
Regards,
Ruslan
|
|
|
|
|
Hi RtyBase,
thanks for your work.
I would like to advice you about a bug on the m_BuffersDone variable that you use in the while loop inside "_Stop" function. m_BuffersDone is never initialized thus having an undefined value when "_Stop" function is called, inside the while loop which is right after waveInReset() call. I put the statement m_BuffersDone=0 in the "_Start" function right before the second call to waveInAddBuffer(): in this way no more problems arises stopping the recording.
But there is another trouble that I don't think is a bug. This is the behavior:
my application, start and stop to record automatically, based on the amount of noice the mic reaches: if it reaches noise over a pre-set level, it start to record; if it reaches noise below the same level, it stops recording. Now it happens that randomly the "++_this->m_BuffersDone;" under MM_WIM_DATA case, will not be reached even though the "waveInReset(this->m_WaveInHandle)" has been called (and m_SIG is set to EXIT_SIG). In this way the "while (this->m_BuffersDone < 2)" inside "_Stop" function will become an infinite loop. (Consider that I put a lot of TRACE in those two involved functions). I do not understand why this happens.
Furthermore, can you explain me why have I to expect that "_this->m_BuffersDone" have to reach the value of 2? Means that have I to expect one MM_WIM_DATA for each buffer built with waveInPrepareHeader()/waveInAddBuffer()? (Sorry, I do not found this explanation inside VC2003).
Last question: when I play what I have recorded thanks to your work, I would like to have a parametric equalizer. Any link, any idea, any articles somewhere in the web?
Thanks and regards, GianniGP
|
|
|
|
|
Hi,
Did you change the original code before you encountered "m_BuffersDone" stuff? The whole idea is; it is initialized to ZERO inside the recording thread, see the code:
-------------------------------------------------------------
DWORD WINAPI CWaveINSimple::waveInProc(LPVOID arg) {
...
while (GetMessage(&msg, 0, 0, 0) == 1) {
switch (msg.message) {
...
// Main thread is opening the WAVE device.
case MM_WIM_OPEN:
_this->m_BuffersDone = 0; // !!!!
break;
...
}
}
...
}
-------------------------------------------------------------
Furthermore, if the thread fails to create or WaveIn fails to open:
-------------------------------------------------------------
waveInThread = CreateThread(NULL, 0, pStartRoutine, (PVOID) this, 0, &dwThreadID);
if (!waveInThread) {
this->m_Receiver = NULL;
throw "Can't create WAVE recording thread.";
}
CloseHandle(waveInThread);
// Open the WaveIN Device, specifying Thread's ID as a callback.
err = waveInOpen(&this->m_WaveInHandle, this->m_nWaveDeviceID, &this->m_waveFormat, dwThreadID, 0, CALLBACK_THREAD);
//!!!!! MIND THE dwThreadID OF THE THREAD
if (err) {
// Open failed, say to Thread to stop.
this->m_SIG = EXIT_SIG;
this->Close(4);
throw "Can't open WaveIN Device.";
}
this->m_SIG = CONTINUE_SIG;
-------------------------------------------------------------
then exception is thrown and proper clean-up is performed. I have unit tested this works fine.
Regarding:
>>can you explain me why have I to expect that "_this->m_BuffersDone" have to reach the value of 2
Because original code is using two buffers for recording:
-------------------------------------------------------------
...
err = waveInAddBuffer(this->m_WaveInHandle, &this->m_WaveHeader[0], sizeof(WAVEHDR));
...
err = waveInAddBuffer(this->m_WaveInHandle, &this->m_WaveHeader[1], sizeof(WAVEHDR));
-------------------------------------------------------------
So, if you add more buffers or use just one, then the "while (this->m_BuffersDone < 2)" check should be adjusted. Additionally, i would recommend using "Stop()" (not "_Stop()") which is thread-safe and "reacts" better on fast start/stop switches.
Regarding "equalizer", do you mean de-composition in frequencies using Fourier series?
Regards,
Ruslan
|
|
|
|
|
Thanks for your quick reply.
1) I think my brain is in a loop too. Yes, you are right: m_BuffersDone is initialized under MM_WIM_OPEN. Based on your reply, I figured out that your waveInProc() doesn't receive MM_WIM_OPEN becuse, in my application, I already opened the same waveform-audio input device in another thread (this thread is monitoring the sound level to decide to start recording or not). Thus, this is solved (infact I wrote "potential bug" ).
2) Thanks for the explanation about how many MM_WIM_DATA will be received after a waveInReset() call. Also, I'm using the "Stop": I referred to "_Stop" just to be more fast in the problem explanation.
3) After understood the problem on topic 1) above, I re-analyzed the situation and there is no reason by which your waveInProc() sometimes doesn't receive both the MM_WIM_DATA with EXIT_SIG as flag after a waveInReset() call (remaining the the while loop for ever). It happens even without my "sound level monitoring thread". Have you any idea? (Don't get crazy for this).
4) Parametric Equalizer: yes I mean de-composition in frequencies using Fourier series able to modify what I'm going to listen (like Windows Media Player or WinAMP has). I'm looking at the link DJ_T-O sent to me, but I do not know if I will be useful for what I need. If you use it, are you using to modify what you are listening?
Thanks and best regards.
|
|
|
|
|
(3) First of all, make sure:
- volatile int m_SIG;
- volatile unsigned char m_BuffersDone;
are still declared "volatile" in order to avoid compiler to optimize usage of those attributes.
Additionally, try this version:
----------------------------------------------------
case MM_WIM_DATA:
if ((((WAVEHDR *)msg.lParam)->dwBytesRecorded) && (_this->m_Receiver)) {
_this->m_Receiver->ReceiveBuffer(((WAVEHDR *)msg.lParam)->lpData,
((WAVEHDR *)msg.lParam)->dwBytesRecorded);
}
++_this->m_BuffersDone;
if (_this->m_SIG != EXIT_SIG) {
err = waveInAddBuffer(_this->m_WaveInHandle, (WAVEHDR *)msg.lParam, sizeof(WAVEHDR));
if (!err) --_this->m_BuffersDone;
}
break;
----------------------------------------------------
If problem still persists then ... might be the driver causing this? In any case please let me know if this code solved the problem.
(4) >>If you use it, are you using to modify what you are listening?
Nope, just to draw frequencies charts.
Regards,
Ruslan
|
|
|
|
|
Have a look at
http://www.codeproject.com/audio/waveInFFT.asp
Hope it helps.
CU
T-O
|
|
|
|
|
Yep, that is a good source ... I have used it as well
Thx DJ_T-O!
Regards,
Ruslan
|
|
|
|
|
First of all - thanks for you code.
I do have a one problem...
I would like to acquire from professional soundcard(RME soundcard 24bit 192Khz) using your code, how can reuse what you have done?
Can you help me ?Sorry for my english.
I really need help on this one.
thanks.
|
|
|
|
|
Hi,
That's a good question actually. I don't believe that the application doesn't work on your sound card as it is. It is just the matter of predefined configurations:
this->m_waveFormat.wFormatTag = WAVE_FORMAT_PCM;
this->m_waveFormat.nChannels = 2;
this->m_waveFormat.nSamplesPerSec = 44100;
this->m_waveFormat.wBitsPerSample = 16;
which your sound card should really support.
However, I understand that you want the whole 24bits PCM from your sound card, not just 16bits PCM . Well, in this case you need to re-define those values (mentioned above). The only problem with this is, I am using Blade interface of the LAME API, which is more simplistic. So, Blade interface supports "beEncodeChunk" function to encode passed PCM. The input for the PCM is expected to be "PSHORT pSamples", this is a pointer to SHORT which is 16bits, so input is expected 16bits PCM. Here you may have troubles. I think you will need to investigate lower level LAME API or to do some math to convert 24bits PCM into 16bits PCM in order to resolve this problem.
Regards,
Ruslan Ciurca
|
|
|
|
|
Hi,
First, Thank's to the developer of this project. It is very useful.
After modifying your code to record directly without giving any information I obtain sometimes an exception caused by the last line "CWaveINSimple::CleanUp()"
can you please explain me why?
Samiro
|
|
|
|
|
Hi Samiro,
Well, it depends on what have you modified. If you redefined the type of the m_arrWaveINDevices member of the class then I presume the problem is here:
vector<cwaveinsimple*>::iterator itPos = m_arrWaveINDevices.begin();
for (; itPos < m_arrWaveINDevices.end(); itPos++) {
delete *itPos; // !!! Here the problem may be !!!
}
And the reason is due to the fact that m_arrWaveINDevices vector was declared as a collection of pointers to objects, not actual copies o objects (in which case "delete *itPos" may fail).
Additionally, make sure that the following is declared outside the body of the class (in case you deleted this):
vector<cwaveinsimple*> CWaveINSimple::m_arrWaveINDevices;
QMutex CWaveINSimple::m_qGlobalMutex;
volatile bool CWaveINSimple::m_isDeviceListLoaded = false;
Those members of the CWaveINSimple class are declared static (!!!) and this is the way (in C++) to initialize them.
Regards,
Ruslan
|
|
|
|
|
Hi,
First of all - thanks for you code, it's very good.
but I do have a one problem...
In some cases, after I'm stopping the recording, I see (or hear, actually) that the last second or so is missing.
is it possible that if I call device->stop() to soon then the last buffer doesn't reach the receivebuffer function ?
I really need help on this one.
thanks.
|
|
|
|
|
Hi,
Well, yes and no at the same time.
A. When you call "device->stop()", this
- tells to the recording thread, of the device, that recording is about to stop (no more buffers will be passed to the device to handle recorded PCM),
- says to the device to un-queue all the queued buffers, which are passed to the ReceiveBuffer(...) anyway. Well, now it depends on the physical device if it succeeded to write any (or all remaining) PCM in those buffers (to be un-queued).
- "device->stop()" waits untill all the queued buffers are processed (while un-queuing).
- close the device.
For more details see "CWaveINSimple::_Stop()" and "CWaveINSimple::waveInProc(LPVOID arg)". I am sorry for the comment in the code saying "(via MM_WIM_DONE)", it surely should be "(via MM_WIM_DATA)".
So, no, all the remaining sound buffers are processed correctly, even last ones, from the technical point of view (exactly what "device->stop()" should do).
B. And yes, it depends on when you stop the device, because last sound buffer (which you hear while recording) may not be the last sound buffer passed to the ReceiveBuffer(...) (considering what is written above in "A"). To resolve this, you probably need to wait for a short while before calling "device->stop()" (this short while depends on the latency of the physical device or sound driver). Alternatively, you can reduce the size of the buffers (supposing that physical device or sound driver is optimized for smaller buffer size) passed to the device. See in the constructor "CWaveINSimple::CWaveINSimple(...)" the following line:
this->m_WaveHeader[1].dwBufferLength = this->m_WaveHeader[0].dwBufferLength = this->m_waveFormat.nAvgBytesPerSec << 1;
Set something more appropriate, instead of "this->m_waveFormat.nAvgBytesPerSec << 1", but make sure the value is divisible by "m_waveFormat.nBlockAlign", in this case 4. Or even better, use "magical" numbers like 2^N, where N >= 9 (otherwise buffer size is too small and CPU may be at a higher usage rate).
Regards,
Ruslan Ciurca
|
|
|
|
|
Hi,
I haven't looked at your code in detail but it is possible to write code that will terminate a recording without losing any bufferred data. What you do is this:
1. Call waveInStop ()
2. Wait for the Receiver to process any remaining bufferred data
3. Call waveInReset ()
4. Call waveInClose ()
The tricky part is knowing when step (2) is complete. The way I do this is (in effect) to post a special message to the receiver thread after calling waveInStop () . Then, when I see this message in the receiver thread, I know my job is done. Calling waveInReset before this point will lose some audio that you otherwise would have captured.
|
|
|
|
|
Hi Paul,
The difference between waveInStop() and waveInReset() is:
- waveInStop() - stops audio input, the currently used for recording buffer is market as done and returned to the application, any other queued buffers stay in the queue. So, technically it costs nothing to call waveInStart() next time, it's like pausing the recording.
- waveInReset() - stops audio input, the currently used for recording buffer is market as done and returned to the application, any other queued buffers are also marked as done and returned to the application. And yes, current position is reset to zero (waveInGetPosition(...), current position is a ~ almost a recording time).
So, technically, calling waveInStop() isn't mandatory.
Regards,
Ruslan
|
|
|
|
|
Hi,
Thanks for your response. The point I was trying to make is that calling waveInReset without first calling waveInStop discards any buffers 'in transit' in the audio system. That's why ran9 is losing the last second or so of his recording. I have tested my own code, which does things in the way I outlined in my previous post, and I don't lose any audio at the end of the recording.
|
|
|
|
|
Paul,
But from buffers 'in transit' in the audio system, there always one is filled (to be fired out) or partially filled, while others are empty. So, waveInStop just tells to the driver to fire out 'filled or partially filled' buffer (currently processed) and to keep rest buffers in queue empty and they will not be processed by the driver until next waveInStart. That is what MSDN says:
"If there are any buffers in the queue, the current buffer (!!!) will be marked as done (the dwBytesRecorded member in the header will contain the length of data), but any empty buffers (!!!) in the queue will remain there."
WaveInReset is doing almost the same, except it also tells to the driver to fire out rest buffers from the queue, but they are empty anyway.
Could you please elaborate with more details your position? I must admit I may be wrong (at the end, I also tested the code and haven't mentioned any loses ... different sound drivers, manufactures?), so it's purely a technical curiosity.
Regards,
Ruslan.
|
|
|
|
|