Sunday, September 7, 2014

Voice Navigation Apps should have a “beep” button to test audio!

Is this thing on?

Voice-Guided Navigation is a magnificent technology that turns into something worse than useless, when it suddenly stops voice-guiding for no reason.

Sometimes I'm driving, with my phone playing music and providing Voice-Guided Navigation, via the USB port in my car. When I pause the music, the Voice-Guided Navigation sometimes (but not always) stops coming out of the speakers as well; I think it has something to do with whether or not it was actually saying something at the time I pause the music.

Whatever is going on, I have absolutely no way to know the state of the Voice-Guided Navigation. Will the instructions for the next turn come out through the speakers? Or anyplace else? Or will there be only silence as I pass one turn after another, venturing deeper and deeper into some scary farm fields in darkest Oregon?

There is no way to tell.

If there were simply a button in the navigation app that goes beep” when you press it, making a sound through whatever the current audio output is, I could at least tell if I need to fiddle with unplugging and re-plugging the USB connection until I get the music on/music off/navigation combination that I want.

I can't even really debug the problem because without a beep” button the only way I can get the navigation app to make a sound is to drive around until it thinks it needs to tell me something. Is this thing on? How many wrong turns do I need to make before I can tell if it made a sound, or definitely failed to make a sound, and get a bit of information so I can diagnose the problem?

It kills me that these apps (Google Maps, Apple Maps, Waze) have invested all this time, effort and money into getting all the maps and route creation working, yet some dumb thing like keeping the music and voice navigation separately controllable, with some way to know what is on and what is off, makes them semi-useless in practice.

Just gimme a button that goes beep” so I can confirm what the audio is doing!

Sheez Louise.

Labels: , , , , , , , , , , , ,

 


 

Saturday, July 26, 2014

Oh...

A Thing my Brain Failed to Think

There needs to be a new word, for the feeling you get when you think, How could I have not realized this, back then? This feeling crept up on me when someone recently pointed out to me some WebRTC audio level code at https://code.google.com/p/webrtc/source/browse/trunk/webrtc/voice_engine/level_indicator.cc. The feeling was strong, because not only is it a solution to the problem I was wrangling with at http://wilsonmichaelpatrick.blogspot.com/2013/06/eventual-optimization.html, it solves it in a much better way.

Of course if you're trying to map audio levels, roughly, into a range of values to display in a meter, you don't bother with expensive log() calls at all. You simply bucket the inputs into ranges, and map those ranges into predetermined values for the meter.

How could I have not realized this, back then?

Fortunately this feeling does not strike me too often, but when it does, it's disconcerting.

Labels: , , , , , , ,

 


 

Saturday, July 19, 2014

Respect the Back-Button

♫ R - E - S - P - E - C - T... 
...T - H - E   B - A - C - K   B - U - T - T - O - N 

How many times has this happened to you: while perusing a news site, you click on a link that interests you.One moment before your requested page loads, you see a second link that interests you.However,you cannot open the second link in a new page quickly enough.It disappears.

What I would like to do in this situation is read the first article I requested, then click the back-button to go to the previous page.From there I would like to proceed to the second interesting link.

Often this does not work because when you return, the list of items where your second article once appeared is now re-populated with an entirely new set of links.The one you wanted is nowhere to be found.

That's disappointing.

I suppose the system is acknowledging that you did not click on the link the first time, and tries to re-populate the list with better click bait for you.That would be reasonable if this was a new visit to the page.However, this is unsuitable for a back-button return to the page.When I click the back-button, I expect to find something I saw there previously.

Summary: Websites need to respect the back-button.
Score: One step back, please.
Moral: Buttons should do what you expect them to do.



Labels: , , , , ,

 


 

Sunday, June 8, 2014

Indie Folk Sucks

Double plus ungood.

Its June 8, 2014, and only 8 results are returned when searching for “indie folk sucks” on Google.

https://www.google.com/#q=%22indie+folk+sucks%22

Wow.

It’s pretty hard for anything to get so few results, without actually being impossible for a person to think.

What kind of newspeak is indie folk, that it elicits so little negative reaction?

The weird thing is, indie folk sucks.

Summary: Indie folk sucks.
Score: 8 results.
Moral: Indie folk sucks.


Labels: ,

 


 

Sunday, June 16, 2013

snprintf() is not Foolproof

Something should have been done about the symbol-to-letter ratio of this post.

Presenting snprintf():

int snprintf(char * restrict str, size_t size, const char * restrict format, ...);

For those who are not familiar with snprintf(), it is used to create formatted strings in a buffer, just like the similarly named sprintf(). However, the man page for snprintf() and sprintf() indicates some significant differences:
  • snprintf()... will write at most [size]-1 of the characters printed into the output string (the [size] th character then gets the terminating '\0'); if the return value is greater than or equal to the [size] argument, the string was too short and some of the printed characters were discarded. The output is always null-terminated.”
  • snprintf() returns “the number of characters that would have been printed if the [size] were unlimited... not including the final '\0'
What all this means is that snprintf() acts like a somewhat safer version of sprintf() from the point of view of preventing buffer overflows. I’ve used it a lot in sections of code where performance is not a major concern, but buffer overflows are (in fact buffer overflows are always a major concern.)

But “safer” is one thing, and “foolproof” is another.

I was reminded of the difference the other day when creating some unit tests for code that uses snprintf(). For no particular reason other than to get the test to compile, I just picked some random values and ran it, and was surprised to see a section of code something like this...

    char someBuf[1];
    int nBufUsed = 2;
    int nBufAvailable = 1;
    if(snprintf(someBuf + nBufUsed, nBufAvailable - nBufUsed, "Hello") !=
       strlen(someBuf + nBufUsed))
    {
        return -1;
    }
    return 0;

...happily run without complaining at all, when I was (incorrectly) expecting it to catch the fact that "Hello" was being written to a 1-byte buffer. (That’s obviously not the exact code, and for other reasons those catastrophically buggy values would not have cropped up in the real program, but it illustrates the point.)

So what happened? The size_t size parameter to snprintf() is unsigned, so if you’ve got a bug that causes a negative value to be passed in (in this case nBufUsed was greater than nBufAvailable), it will be interpreted as a very large positive value for the buffer size. This can result in the very buffer overflow you were trying to avoid by using snprintf().

You can never be too careful.

PS: I'm not in the habit of passing negative size values around except in testing.

Summary: snprintf() is helpful, but not foolproof.

Score: Either -1 or 18446744073709551615.

Moral: Mind that size_t size parameter to snprintf(), for it is not immune to “garbage in, garbage out.”

Labels: ,

 


 

Sunday, June 9, 2013

The Case of the Noisy Codec

Load this up with jokes on the re-write!

While I was working on a song crafting application called SongDasher_Light (https://github.com/wilsonmichaelpatrick/SongDasher_Light) for the iPhone and iPad, I ran across a very intermittent, but absolutely horrible, problem where bursts of noise were being introduced into the AAC encoding of the audio. I really hated it for being intermittent, and I really hated it it for being horrible. You do not want to hear this:



The tall part toward the right is 2048 bytes of very loud noise.

After a great deal of wrangling, I narrowed things down to a difference between the hardware codec and the software codec. Given a certain sets of legitimate (i.e. not contrived) input data, converting audio from PCM to AAC seems to result in junk data in the output. In this case the input was an actual drum beat, not just arbitrary 1’s and 0’s. The problem also occurred with other kinds of legitimate input.

In order to rule out any weird memory corruption or other defect in my project as the cause, I created a separate demonstration program, whose only purpose is to convert the bug-demonstration data to AAC; it is available at https://github.com/wilsonmichaelpatrick/HWCodecGlitch. (Note: This program only demonstrates the problem on a real device, because the hardware codec is not available in the simulator.) When the software codec (kAppleSoftwareAudioCodecManufacturer) is used in the conversion, the AAC output sounds nice. When the hardware codec is used, as it is by default on a real device, the burst of noise is present.

That’s about as much as I was able find out about the issue, which isn’t perfectly convincing, but convincing enough: using the hardware codec with certain inputs results in noise, even in a very simple, stand-alone program. To fix the problem, I just use the software codec even when the hardware codec is available, and I have not seen it since.

I was glad that my own code didn’t seem to be the cause, but it was a lot of work to identify and find a way around this problem.

PS: Turn your volume way down if you plan on listening to the burst of noise; it is drastic.

Summary: I’m convinced enough that it was the hardware codec making noise, not my code.

Score: Software codec 1, hardware codec 0.

Moral: Ruling out your own code as the cause of a problem can be somewhat, but not adequately, satisfying.

Labels: , , , , , , ,

 


 

Sunday, June 2, 2013

Eventual Optimization

You don’t know until you measure!

Let me just throw this out there:

It’s not enough to optimize code in the areas that obviously require optimization, if it is still hobbled by things that aren’t expected to be consuming resources.

As such, I think it’s important to profile running code; it’s very difficult to know what’s using up your resources until you measure. Without actual measurements, there’s more risk of missing significant performance issues by honing all of the smart stuff, and ignoring all of the silly stuff.

Silly stuff like the methods highlighted below:


This is a portion of the results from Xcode profiling SongDasher_Light (https://github.com/wilsonmichaelpatrick/SongDasher_Light) recording a two-minute audio track on an iPhone. Drilling down from the highest-CPU subroutine to some code that I’ve written (i.e. code with high CPU-consumption that I can actually do something about), it turns out GetDBLevel_16Bit is the “winner.” What does GetDBLevel_16Bit do? It makes the little level meter light up when someone makes a noise into the microphone. That’s it. (As far as I know CoreAudio only calculates decibels for output, not input, which is why I’m doing this the hard way here.) It was very surprising to see something so non-essential taking up 25.2% of the total CPU for the whole program. It’s somewhat less surprising when you look at the code in this method, though:

    for(int i = 0; i < nNumFrames; i++)
    {
        (*pflInputDBCur) = flLowpassAlpha * (abs(arrSamples[i])) + 
        (1.0 - flLowpassAlpha) * (*pflInputDBPrev);
        (*pflInputDBPrev) = (*pflInputDBCur);
        
        // DB is 20 log10
        Float32 flCurDB;
        flCurDB = 20.0 * log10(*pflInputDBCur);
        
        if((flCurDB == flCurDB) && (flCurDB != -DBL_MAX))
        {
            if(flCurDB > (*pflInputDB))
                (*pflInputDB) = flCurDB;
        }
    }

Basically, this code is correct. (Please let me know if it’s not!) But it’s a pretty expensive computation to be doing on every single sample of input while recording. (I must have really been avoiding premature optimization on the day I wrote that!) All I’m trying to do here is get a level meter to pass an “eyeball” test by jumping to an appropriate height when it hears a noise. 25.2% of total CPU! What if it just looked at, say, every 8th sample by setting the loop increment to 8, like this:

    for(int i = 0; i < nNumFrames; (i += nIncrement))
    {
        // etc...
    }

Would that help? In fact it helps a lot, which I’ll get to shortly. The point here is that I would never have known to optimize this if the profiler had not put it toward the top of the list. After taking great pains to pre-calculate all the little x-axis crosses where the waveforms can be joined without “popping,” and pre-calculate the precise drum-machine trigger times, and all the other corner-cutting I did to keep things quick, I almost gave it all back (and more) with the input level meter.

After GetDBLevel_16Bit, there’s WriteBufferToCaptureFile, which is used during recording to save the sound to a file. Why is that so high in the list? As it turns out, it was calling this method:

extern OSStatus
ExtAudioFileWrite(ExtAudioFileRef inExtAudioFile,
UInt32 inNumberFrames, const AudioBufferList * ioData)

Instead of this method:

extern OSStatus
ExtAudioFileWriteAsync(ExtAudioFileRef inExtAudioFile,
UInt32 inNumberFrames, const AudioBufferList * ioData)

ExtAudioFileWrite writes to the file right away, and is not meant for time-critical operations like capturing audio. ExtAudioFileWriteAsync batches writes to avoid interrupting the thread it’s called on. The insidious thing is that both of them produce the same result (the audio correctly saved to a file) but have a significant difference in performance. I might never have known I’d accidentally put the wrong one in there if I hadn’t measured the performance.

Having addressed these issues with GetDBLevel_16Bit and WriteBufferToCaptureFile, I ran the profiler again, and got the following results:



Ballpark figures for these improvements, keeping in mind that the two test runs are not exactly identical, and whatever other noise is present from running on the actual device:
  • GetDBLevel_16Bit has gone from 7891.0ms to 961.0ms, making it about 8.2 times faster,
  • WriteBufferToCaptureFile has gone from 2676.0ms to 233.0ms, making it about 11.5 times faster.
Put together, the CPU used by the audio thread as a whole went from 20995.0ms to 10109.0ms, making it about 2.1 times faster. That’s a pretty huge gain for two small fixes; in fact it helped wring out the last bits of “glitchiness” I was seeing running on older devices. And to come back to my original point, the fixes were in code I wouldn’t have thought needed optimizing until I measured.

Summary: After making my best effort to keep the audio processing fast, I found two things I would never have guessed were taking up a large percentage of total processing time, and would not have found them without profiling the running code.

Score: About 2.1 times faster.

Moral: Profiling can help identify performance issues in unexpected places.

Labels: , , , , , ,

 


 

Current Posts *  Voice Navigation Apps should have a “beep” button ... * Oh... * Respect the Back-Button * Indie Folk Sucks * snprintf() is not Foolproof * The Case of the Noisy Codec * Eventual Optimization * The Reentrant Observer Pitfall *  May 2013 * June 2013 * June 2014 * July 2014 * September 2014 *  Subscribe to Posts [Atom]

profile for wilsonmichaelpatrick on Stack Exchange, a network of free, community-driven Q&A sites

© 2013 - 2014 wilsonmichaelpatrick






Powered by Blogger