I have a mp3 player in Delphi.
When the player is running and I change the audio output device, say from line out (speakers) to bluetooth, the player still "sends" the sound to the line out device.
When my bluetooth headset is connected and I start the player, everything is fine and the player "sends" the sound to the bluetooth device. But when I disconnect the bluetooth speakers/headset, the player doesn't play any song, it just skips every song as they won't be "playable".
Is there a way to get this managed?
Every other player, VLC, Winamp, Windows Media Player, are able to handle the change of the output device "on the fly".
For the player I use Delphi XE, but I think this isn't relevant to the problem as itself.
At the time I use BASS library 2.4.11
Thanks for your answers in advance!
MPage
Yes, there is the way.
AIMP player is exactly written in Delphi using BASS library.
If you stop playing, change audio output in preferences and start playing - it sends music to the newly selected sound device.
So, after reading some manuals and doing some test, this is what I have now in my "MediaPause" routine:
// Reset counter
i := 1;
// Reset device list
lstADevices.Clear;
while BASS_GetDeviceInfo(i, ADeviceInfo) do
begin
lstADevices.Add(ADeviceInfo.name);
inc(i);
end;
intDevice := -1;
for i := 0 to lstADevices.Count - 1 do
begin
if lstADevices[i] = 'Bluetooth A2DP Stereo Audio (Bose AE2w 01.02.00( Stereo ))' then
begin
intDevice := i + 1; // + 1 is important, because the list starts with 0! (for BASS 0 means 'no device')
BASS_GetDeviceInfo(intDevice, ADeviceInfo);
intFlag := ADeviceInfo.flags;
if intFlag = 67108868 then
begin
for j := 0 to lstADevices.Count - 1 do
begin
if lstADevices[j] = 'Speakers (Realtek High Definition Audio)' then
begin
intDevice := j + 1; // The list starts at 0
break;
end;
end;
end;
break;
end;
end;
BASS_Init(intDevice, 44100, 0, Application.Handle, nil);
blnBASS := BASS_ChannelSetDevice(AudioStream, intDevice);
if not blnBASS then
intBASSErrorCode := BASS_ErrorGetCode;
BASS_ChannelPlay(AudioStream, False);
When the program is started and the bluetooth headset is not connected, it will not be enumerated to get into the device list. So I have to enumerate the devices at any necessary time. I wanted to implement this into the "tick counter" for displaying the time (ticks in seconds), but because of the following behavior I didn't do so yet.
I can switch (BASS_ChannelSetDevice) between the bluetooth headset and the speakers once. When I want/have to switch again, to whatever reason, I get the error code 4 which means BASS_ERROR_BUFLOST (The sample buffer was lost).
I tried to find anything according the buffer size, but couldn't find anything.
I check the flag value 67108868 of the bluetooth device, because I couldn't find any other way to check if the device is available or not.
If the bluetooth device has been connected once during the runtime, it will always get enumerated even it is disconnected afterwards. It also has the "enabled" state, so I only found the difference in the flag value.
Packing the above code into the tick counter for the display I could get it managed to change the devices from speakers to headset automatically, which would be the "perfect" way for me, but due to the problem with the "buffer lost" error, I removed it from the tick counter routine.
Like Victoria and Arioch 'The mentioned, I paused the player before changing the device. Like mentioned, it works one time and the next time I run into the "buffer lost" error. This means, there is no error produced, but the output isn't changed then.
As I'm not sure if "my way" is correct in any sense, is there a "more safe" way to change the devices?
MPage
Related
I am developing MIDI Player by referring to the following Web-Page.
http://twocentstudios.com/2017/02/20/bouncing-midi-to-audio-on-ios/
I don't do any recording, I just want to play the SMF file.
However, when I run setPreload (true), it says "ASSERTION FAILED: Preroll mode set during render" and my app hangs.
I searched for "Preroll mode set during render" but couldn't find any valid information.
Please help someone.
EDIT:
hi, #dspr.
The percussion sounds even if I don't do "AudioUnitSetProperty (kAUMIDISynthProperty_EnablePreload: 1)".
I think this is because the BANK for percussion is automatically assigned to ch.10.
However, in this state, the piano and guitar and others do not sound.
AVAudioUnitMIDI Instrument needs kAUMIDISynthProperty_EnablePreload to analyze which tone is assigned to which track in the SMF file, right?
Which method does AVAudioUnitMIDIInstrument use to preload SMF files?
(1) AudioUnitSetProperty (kAUMIDISynthProperty_EnablePreload: 1) to AVAudioUnitMIDISynth
(2) << How to preload? >>
(3) AudioUnitSetProperty (kAUMIDISynthProperty_EnablePreload: 0) to AVAudioUnitMIDISynth
(4) Start AVAudioSequencer
MIDI Player uses the kAUMIDISynthProperty_EnablePreload property of MIDISynth for that purpose. See the Apple comment about it below. Note the It should only be used prior to MIDI playback and must be set back to 0 before attempting to start playback sentence at the end :
/*!
#constant kAUMIDISynthProperty_EnablePreload
#discussion Scope: Global
Value Type: UInt32
Access: Write
Setting this property to 1 puts the MIDISynth in a mode where it will attempt to load
instruments from the bank or file when it receives a program change message. This
is used internally by the MusicSequence. It should only be used prior to MIDI playback,
and must be set back to 0 before attempting to start playback.
*/
EDIT : frankly, I'm a little bit reserved about your link
One strategy I haven’t tried would be to pitch shift the MIDI up one octave, play it back at 2x, record it at 88.2kHz, then downsample to 44.1kHz. AVAudioSession presumably can’t go past 48kHz though.
Clearly, the person who wrote that has a very poor knowledge about audio and sampling. Playing a MIDI song transposed one octave up at double tempo is really not equivalent than playing the same recorded in audio at double speed whatever you make the recording at 88.2kHz or any other sample rate. As a simple example, what happens is the file contains a drum set ? A snare drum (40) will become a Chinese cymbal (52) played two times slower ?
As I can understand this post, the described hack has for unique purpose to make recording. So if you simply want to play your MIDI file back you can certainly find a simpler and better example.
I using Delphi XE and DSPack 2.3.3.
I'm maintaining a desktop application with Webcam capture. The user is able to capture pictures of visitors and documents.
Everything is working fine. One of our largest customers is moving to Citrix 7.13 and the webcam is freezing.
Their support contacted Citrix and Citrix Engineering would like to know if the application is calling the IMediaFilter::SetSyncSource method to explicitly set the “reference clock” to the filter graph in their code regarding the webcam capturing.
I made a few test and in fact there's a call when the rendering starts. You can see the call when running DSPack demo "VideoCap":
// now render streams
with CaptureGraph as IcaptureGraphBuilder2 do
begin
// set the output filename
SetOutputFileName(MEDIASUBTYPE_Avi, PWideChar(CapFile), multiplexer, Writer);
// Connect Video preview (VideoWindow)
if VideoSourceFilter.BaseFilter.DataLength > 0 then
RenderStream(#PIN_CATEGORY_PREVIEW, nil, VideoSourceFilter as IBaseFilter,
nil , VideoWindow as IBaseFilter);
// Connect Video capture streams
if VideoSourceFilter.FilterGraph <> nil then
RenderStream(#PIN_CATEGORY_CAPTURE, nil, VideoSourceFilter as IBaseFilter,
nil, multiplexer as IBaseFilter);
// Connect Audio capture streams
if AudioSourceFilter.FilterGraph <> nil then
begin
RenderStream(nil, nil, AudioSourceFilter as IBaseFilter,
nil, multiplexer as IBaseFilter);
end;
end;
CaptureGraph.Play;
According to DSpack source comments:
{ The reference clock has changed. The filter graph manager sends this event
when its IMediaFilter.SetSyncSource method is called.}
property OnGraphClockChanged: TNotifyEvent read FOnGraphClockChanged write FOnGraphClockChanged;
And in fact OnGraphClockChanged gets fired after CaptureGraph.Play is called.
Is it possible to avoid calling SetSyncSource?
Do you know if this will solve this issue?
TIA,
Clément
Quoting the MSDN page on IMediaFilter::SetSyncSource:
When the graph runs, the Filter Graph manager calls this method on every filter in the graph, to notify them of the graph reference clock. Use this method to store the IReferenceClock pointer. Increment the reference count on the stored pointer. Before the filter is removed from the graph, the Filter Graph Manager calls SetSyncSource again with the value NULL.
This means that SetSyncSource() is called regardless of your code. If the filter you are using is stalling because of the filter graph calling it's SetSyncSource() method, then this seems like a defect in the filter.
In this case a potential workaround would be to create a wrapper filter around the capture filter in question and to forward all method calls except SetSyncSource(). But most likely the problem has other origin.
My bet is that setting the reference clock to NULL will solve the problem. To do this you have to query IMediaFilter interface from IFilterGraph and call SetSyncSource(NULL). This will disable the entire timing for the graph and render every multimedia sample as fast as it is generated.
More details on live source filter graphs can be found at this MSDN page - https://msdn.microsoft.com/en-us/library/windows/desktop/dd390645(v=vs.85).aspx
I'm looking for a way to detect how long a key has been held down in a Delphi project and warn user.
I'm working on a chat program and need to see if the person is holding down a letter, like the W key, to spam that chat box. I'll give sample what trying to do in Delphi 7:
//Looking up if key in use and held for lets say 20 seconds
if (GetAsyncKeyState(Byte(VkKeyScan('W'))) shl 20) <> 0 then
begin
ShowMessage('W Key Held down too long!');
end;
I'm not sure whether GetAsyncKeyState will give me that information, though. If it doesn't, what will?
Windows does not report the duration of how long a key has been held down, only that the same WM_KEY... message is being repeated for a key that is held down. You have to keep track of the duration yourself manually. When you detect a WM_KEYDOWN message with its wParam bit 30 set to 1, if you are not tracking that key yet, start tracking it and store the current system/tick with it, otherwise grab the current system/tick time, calculate the duration, and act accordingly. When you receive a WM_KEYUP message, stop tracking that key if you are tracking it.
I use the StopWatch class in the StopWatch unit to check for timing. Here's how you could try using it for what you're trying to do:
uses StopWatch;
//in the Form's private declaration:
StopWatch : TStopWatch;
//in the Form's onCreate:
StopWatch := TStopWatch.Create(nil);
//in the Form's onDestroy:
StopWatch.Free();
//in the form/box onKeyDown:
StopWatch.Start();
//in the form/box onChange:
if (StopWatch.ElapsedMiliseconds > 1000)
ShowMessage('W Key Held down too long!');
//in the form/box onKeyUp:
StopWatch.Stop();
StopWatch.Start();
StopWatch.Stop();
There are likely lots of other ways to accomplish what you're trying to do though. But for a quick try this should work.
The reason I stop and start the stopwatch again onKeyUp is to clear out ElapsedMiliseconds, in case the user changes the box with some method other than the keyboard, after having been alerted -- so they won't get alerted twice.
Right-o, I'm working on implementing DirectSound in our Delphi voip app (the app allows for radios to be used by multiple users over a network connection)
Data comes in via UDP broadcasts.
As it is now, we go down on a raw data level and do the audio mixing from multiple sources ourselves and have a centralized component that is used to play all this back.
The app itself is a Delphi 5 app and I'm tasked with porting it to Delphi 2010. Once I got to this audio playback part, we concluded that it is best if we can get rid of this old code and replace it with directsound.
So, the idea is to have one SecondaryBuffer per radio (we have one 'panel' each per radio connection, based on a set of components that we create for every specific radio) and just let these add data to their respective SecondaryBuffers whenever they get data, only pausing to fill up half a second worth of audio data in the buffer if it runs out of data.
Now, I'm stuck at the part where I'm adding data to the buffers in my test-application where I'm just trying to get this to work properly before I start writing a component to utilize it the way we want.
I'm using the ported DirectX headers for Delphi (http://www.clootie.ru/delphi/download_dx92.html)
The point of these headers is to port over the regular DirectSound interface to Delphi, so hopefully non-Delphi programmers with DirectSound may know what the cause of my problem is as well.
My SecondaryBuffer (IDirectSoundBuffer) was created as follows:
var
BufferDesc: DSBUFFERDESC;
wfx: tWAVEFORMATEX;
wfx.wFormatTag := WAVE_FORMAT_PCM;
wfx.nChannels := 1;
wfx.nSamplesPerSec := 8000;
wfx.wBitsPerSample := 16;
wfx.nBlockAlign := 2; // Channels * (BitsPerSample/2)
wfx.nAvgBytesPerSec := 8000 * 2; // SamplesPerSec * BlockAlign
BufferDesc.dwSize := SizeOf(DSBUFFERDESC);
BufferDesc.dwFlags := (DSBCAPS_GLOBALFOCUS or DSBCAPS_GETCURRENTPOSITION2 or DSBCAPS_CTRLPOSITIONNOTIFY);
BufferDesc.dwBufferBytes := wfx.nAvgBytesPerSec * 4; //Which should land at 64000
BufferDesc.lpwfxFormat := #wfx;
case DSInterface.CreateSoundBuffer(BufferDesc, DSCurrentBuffer, nil) of
DS_OK: ;
DSERR_BADFORMAT: ShowMessage('DSERR_BADFORMAT');
DSERR_INVALIDPARAM: ShowMessage('DSERR_INVALIDPARAM');
end;
I left out the parts where I defined my PrimaryBuffer (it's set to play with the looping flag and was created exactly as MSDN says it should be) and the DSInterface, but it is as you might imagine an IDirectSoundInterface.
Now, every time I get an audio message (detected, decoded and converted to the appropriate audio format by other components we have made that have been confirmed to work for over seven years), I do the following:
DSCurrentBuffer.Lock(0, 512, #FirstPart, #FirstLength, #SecondPart, #SecondLength, DSBLOCK_FROMWRITECURSOR);
Move(AudioData, FirstPart^, FirstLength);
if SecondLength > 0 then
Move(AudioData[FirstLength], SecondPart^, SecondLength);
DSCurrentBuffer.GetStatus(Status);
DSCurrentBuffer.GetCurrentPosition(#PlayCursorPosition, #WriteCursorPosition);
if (FirstPart <> nil) or (SecondPart <> nil) then
begin
Memo1.Lines.Add('FirstLength = ' + IntToStr(FirstLength));
Memo1.Lines.Add('PlayCursorPosition = ' + IntToStr(PlayCursorPosition));
Memo1.Lines.Add('WriteCursorPosition = ' + IntToStr(WriteCursorPosition));
end;
DSCurrentBuffer.Unlock(#FirstPart, FirstLength, #SecondPart, SecondLength);
AudioData contains the data in my message. Messages always contain 512 bytes of audio data.
I added the Memo1.Lines.Add lines to be able to get some debug output (since using breakpoints doesn't quite work, as directsound keeps playing the contents of the primary buffer regardless)
Now, when I'm playing my DSCurrentBuffer using the looping flag (which according to hte MSDN docs is enough to make it a Streaming Buffer) and having this code work out as it wants, my output text in the Memo show that I am being allowed to write up until the end of the buffer... But it doesn't wrap.
SecondPart is always nil. It never ever wraps around to the beginning of the buffer, which means I get the same few seconds of audio data playing over and over.
And yes, I have scoured the net for components that can do this stuff for us and have concluded that the only reliable way is to do it ourselves like this.
And yes, the audio data that this app plays is choppy. I'm holding off on writing the half-a-second buffering code until I can get the write-to-buffer code to wrap as it should :/
I have been reading that people suggest keeping track of your own write cursor, but from what I read Lock and Unlock should help me bypass that need.
I'd also rather avoid having to have two buffers that I alternate between back and forth (or a split-buffer, which would essentially be the same thing, only a bit more complex in writing)
Any help greatly appreciated!
So I figured out the problem ^^;
Pretty simple too.
DSCurrentBuffer.Unlock(#FirstPart, FirstLength, #SecondPart, SecondLength);
I thought I was supposed to just pass along the same pointers to Pointers that Lock() had required.
Changing it to
DSCurrentBuffer.Unlock(FirstPart, FirstLength, SecondPart, SecondLength);
Solved the issue and the buffer now wraps correctly.
Sorry for wasting your time, but thanks anyway ^^;
A few things that might cause this:
Memo1.Lines.Add should only be called from the main thread (the thread that initialized the VCL GUI). Use TThread.Synchronize for this (easier), or an intermediate buffer that is thread safe and preferably lock-free (faster; thanks mghie for this hint).
Unlock should be in a finally section like below, because if an exception gets raised, you never unlock the buffer, see code sample below.
You should log any exceptions taking place.
Sample code:
DSCurrentBuffer.Lock(0, 512, #FirstPart, #FirstLength, #SecondPart, #SecondLength, DSBLOCK_FROMWRITECURSOR);
try
//...
finally
DSCurrentBuffer.Unlock(#FirstPart, FirstLength, #SecondPart, SecondLength);
end;
--jeroen
I am using the following code to copy text to the clipboard:
Clipboard.Open;
try
Clipboard.AsText := GenerateClipboardText;
finally
Clipboard.Close;
end;
Seemingly at random I get "Cannot open clipboard: Access Denied" errors. I'm guessing that these errors are caused by other application locking the clipboard, but I never seem to be doing anything with other applications that should cause the locks.
Strangely my users seem to be reporting more of the errors with Vista and Windows 7 than with XP.
Is there a way to check if the clipboard is locked before trying to access it?
This is not a Delphi problem. Because the clipboard can be locked any moment, even if you check, if the clipboard is currently not locked, it might become locked directly after the check.
You have two possibilities here:
Don't use the Delphi clipboard class. Instead use raw API functions, where you have a little more fine-grained control over possible error situations.
Expect your code to fail by adding an exception handler. Then add some retry code, i.e. retry to set the text three times, perhaps with exponential backoff, before throwing your own error.
I'd recommend the second solution, because it'd be the more Delphi-like approach and in the end will result in cleaner code.
var
Success : boolean;
RetryCount : integer;
begin
RetryCount := 0;
Success := false;
while not Success do
try
//
// Set the clipboard here
//
Success := True;
except
on E: EClipboardException do
begin
Inc(RetryCount);
if RetryCount < 3 then
Sleep(RetryCount * 100)
else
raise Exception.Create('Cannot set clipboard after three attempts');
end else
raise; // if not a clipboard problem then re-raise
end;
end;
Strangely my users seem to be
reporting more of the errors with
Vista and Windows 7 than with XP
This may have to do with how Vista/Win7 deal with clipboard viewer notification. While they still support the XP "clipboard viewer chain", which sends one notification message that must be re-sent to each listener in turn (and if one app fails to do this, the other apps aren't notified). Starting with Vista, apps are notified directly. And there's nothing to keep them from trying to access the clipboard all at once.
Analogy: I have 3 children. I have a cake. With XP rules, I tell the oldest child to have some cake, then tell the next oldest child to have a slice. She gets her slice, tells her brother, he gets his, and tells his brother, who gets his, and everything proceeds in an orderly fashion.
Problem: The middle child takes the cake to his room, doesn't tell the youngest, and the youngest misses out.
With Vista/Windows7, that system still exists. But newer apps can request to be notified immediately, by me, as soon as the cake arrives in the kitchen. I shout "cake is ready!" and they all show up at the same time and try to grab some. But there's only one serving knife, so they have to keep reaching for the knife, failing to get it, and waiting for the next opportunity.
Try to check GetClipboardOwner, if it's not null and not your Application.Handle, you cannot Open to modify it's content.
And even it seems good to go, it might not be anymore when you actually do it.
So add a try except in a loop until you get it or give up nicely (notifying the user for instance).
There is no way to check for something and then depending on the result do something else with the expectation that it could not fail, because unless the check and the action are one atomic operation there is always the possibility that another process or thread does the same in parallel.
This holds whether you try to open the clipboard, open a file, create or delete a directory - you should simply try to do it, maybe several times in a loop, and gracefully handle errors.
First of all please notice that this probably isn't a problem in your application. Other applications locked the clipboard or messed up the notification chain and now your application fails to access it. When I do have problems like this I restart the computer and they magically go away... well... at least until I run again the application that creates the problem.
This code (not checked in Delphi) may help you. It won't fix the problem is the notification chain is broken (nothing except a PC restart will ever fix it) but it will fix the problem if an application is locking the clipboard for a while. Increase the MaxRetries if that pesky application keeps the clipboard locked for A REALLY LONG time (seconds):
procedure Str2Clipboard(CONST Str: string; iDelayMs: integer);
CONST
MaxRetries= 5;
VAR RetryCount: Integer;
begin
RetryCount:= 0;
for RetryCount:= 1 to MaxRetries DO
TRY
inc(RetryCount);
Clipboard.AsText:= Str;
Break;
EXCEPT
on Exception DO
if RetryCount = MaxRetries
then RAISE Exception.Create('Cannot set clipboard')
else Sleep(iDelayMs)
END;
end;
Also, it may be a good idea to drop the 'raise' and convert it to a function and use it like this:
if not Str2Clipboard
then Log.AddMsg('Dear user, other applications are blocking the clipboard. We have tried. We really did. But it didn''t work. Try again in a few seconds.');
I guess you are running your app on Win 8 or higher.
Just right-click on your App .exe file, go to Compatibility tab and change compatibility mode on Windows XP or lower versions. It'll work, guaranteed!