comparison ALmixer.c @ 51:e2687188aea5

The evil Apple iOS 5.0 OpenAL regression bug strikes again. A user managed to encounter a case where the OpenAL buffer never unqueues and the workaround gets stuck in an infinite loop which results in the app getting killed by watch dog (crashing). I've added a timeout that will abort the unqueue attempt after 200 milliseconds. But if the buffers could be unqueued but the timeout we picked is too short, then we will regress back to the original problem. All iOS 5 audio users need to test this to make sure their apps don't break again. Please remember to file your Apple bug report if you haven't (bug:10145018), test with iOS 5.1 beta to check for new problems, and report results as necessary. I've also constrained the iOS 5.0 workaround to not run on 5.1 so users can test to see if Apple has fixed the problem. I've also changed the default buffer size to 8k and all the other values accordingly (still targeting about 100k per stream). I encountered some bugs on Mac with 4k buffers. Also running under the memory profile, it appeared that the internal Apple overhead starts to flatten out around 8k which suggests 8k is a better value.
author Eric Wing <ewing@anscamobile.com>
date Wed, 18 Jan 2012 12:17:42 -0800
parents db5bc1c80057
children 8063b19bd40e
comparison
equal deleted inserted replaced
50:db5bc1c80057 51:e2687188aea5
1726 /* 1726 /*
1727 fprintf(stderr, "kCFCoreFoundationVersionNumber: %lf\n", kCFCoreFoundationVersionNumber); 1727 fprintf(stderr, "kCFCoreFoundationVersionNumber: %lf\n", kCFCoreFoundationVersionNumber);
1728 */ 1728 */
1729 /* I needed a C way to get the iOS version at runtime. This is returning 674.0 iOS 5.0 Beta 7. Apple hasn't updated the headers 1729 /* I needed a C way to get the iOS version at runtime. This is returning 674.0 iOS 5.0 Beta 7. Apple hasn't updated the headers
1730 * for these constants since iOS 4.2, so I don't know if the value is also catching 4.3. 1730 * for these constants since iOS 4.2, so I don't know if the value is also catching 4.3.
1731 * iOS 5.0.1 final is returning 675.0.
1731 * TODO: Once I learn which version Apple fixes the bug in, I need to update the range so this check is not run on fixed versions. 1732 * TODO: Once I learn which version Apple fixes the bug in, I need to update the range so this check is not run on fixed versions.
1733 * iOS 5.1 Beta 1 is returning 690.0.
1732 */ 1734 */
1733 if(kCFCoreFoundationVersionNumber >= 674.0) 1735 if(kCFCoreFoundationVersionNumber >= 674.0 && kCFCoreFoundationVersionNumber < 690.0)
1734 { 1736 {
1735 /* For OpenAL experts, this is contrary to what you know, but must be done because the OpenAL implementation is broken. 1737 /* For OpenAL experts, this is contrary to what you know, but must be done because the OpenAL implementation is broken.
1736 Instead of unqueuing buffers on only streaming sources, it appears that alSourcei(source, AL_BUFFER, AL_NONE) is not reliable at all. 1738 Instead of unqueuing buffers on only streaming sources, it appears that alSourcei(source, AL_BUFFER, AL_NONE) is not reliable at all.
1737 In cases where I switch between stream and non-stream on the same source and then stream again, the bug breaks playback on the third playback 1739 In cases where I switch between stream and non-stream on the same source and then stream again, the bug breaks playback on the third playback
1738 and only one buffer plays. 1740 and only one buffer plays.
1740 And then avoid calling (source, AL_BUFFER, AL_NONE) 1742 And then avoid calling (source, AL_BUFFER, AL_NONE)
1741 From past experience, I know it is a bad idea to try to unqueue buffers from a non-streamed source (which is the contrary to OpenAL part), 1743 From past experience, I know it is a bad idea to try to unqueue buffers from a non-streamed source (which is the contrary to OpenAL part),
1742 but this seems to work for this bug. 1744 but this seems to work for this bug.
1743 */ 1745 */
1744 ALint buffers_processed; 1746 ALint buffers_processed;
1747 /* Crap. Another iOS 5.0 problem. It seems our loop workaround below can get in cases where the buffer never clears.
1748 * It appears that in some cases (probably predecoded, but not always which makes it hard), that the buffer never can be unqueued.
1749 * I think it may possibly happen if you never use a source for streaming, and it is also possible it happens after loading or using a certain number of sources.
1750 * So to workaround, we need to abort after a certain amount of time to prevent an infinite loop check.
1751 * Some testing on iPad 2, 122ms is the highest number I've seen so far. So maybe 200ms is the cap?
1752 */
1753 ALuint timeout_counter = ALmixer_GetTicks();
1754 const ALuint MAX_NUMBER_OF_TICKS_TO_WAIT_IN_WORKAROUND = 200;
1755
1745 /* Wow this is the bug that just keeps on sucking. There is even a race condition bug where the unqueue may not actually work. 1756 /* Wow this is the bug that just keeps on sucking. There is even a race condition bug where the unqueue may not actually work.
1746 * So I have to keep doing it until it does. 1757 * So I have to keep doing it until it does.
1747 */ 1758 */
1748 do 1759 do
1749 { 1760 {
1750 ALint temp_count; 1761 ALint temp_count;
1751 1762
1752 /* Wow this is the bug that just keeps on sucking. There is even a race condition bug where the unqueue may not actually work. 1763 /* Wow this is the bug that just keeps on sucking. There is even a race condition bug where the unqueue may not actually work.
1753 * So I have to keep doing it until it does. 1764 * So I have to keep doing it until it does.
1754 * Sleeping for 20ms seems to help. 10ms was not long enough. (iPad 2) 1765 * Sleeping for 20ms seems to help. 10ms was not long enough. (iPad 2).
1766 * Update: A user is hitting this in a tight loop and calling dozens/hundreds of times. The sleep some times seems to result in a stutter.
1767 * My theory is that there is thread contention and sleeps may be queuing up. The workaround seems to be reduce the sleep or eliminate it.
1768 * I prefer to reduce it because fundamentally I am waiting for the Core Audio/OpenAL thread to finish processing so yielding seems like a good idea.
1769 * The flipside is that I hit a lot of OpenAL errors because the commands keep failing and I'm burning CPU unnecessarily.
1770 * So far, the OpenAL errors seem harmless and there are no serious cascading failures I've encountered.
1755 */ 1771 */
1756 ALmixer_Delay(20); 1772 ALmixer_Delay(0);
1757 1773
1758 alGetSourcei( 1774 alGetSourcei(
1759 source_id, 1775 source_id,
1760 AL_BUFFERS_PROCESSED, &buffers_processed 1776 AL_BUFFERS_PROCESSED, &buffers_processed
1761 ); 1777 );
1780 source_id, 1796 source_id,
1781 1, &unqueued_buffer_id 1797 1, &unqueued_buffer_id
1782 ); 1798 );
1783 if((error = alGetError()) != AL_NO_ERROR) 1799 if((error = alGetError()) != AL_NO_ERROR)
1784 { 1800 {
1785 fprintf(stderr, "17bTesting error with unqueuing buffers on Halt (You may be seeing this because of a bad Apple OpenAL iOS 5.0 regression bug): %s\n", 1801 /* Disabling this print because we hit it way too much with the usleep(0) */
1786 alGetString(error)); 1802 /* fprintf(stderr, "17bTesting error with unqueuing buffers on Halt (You may be seeing this because of a bad Apple OpenAL iOS 5.0 regression bug): %s\n", alGetString(error));
1803 */
1787 /* This whole iOS 5.0 bug is so messed up that returning an error value probably isn't helpful. 1804 /* This whole iOS 5.0 bug is so messed up that returning an error value probably isn't helpful.
1788 retval = AL_FALSE; 1805 retval = AL_FALSE;
1789 */ 1806 */
1790 } 1807 }
1791 } 1808 }
1794 source_id, 1811 source_id,
1795 AL_BUFFERS_PROCESSED, &buffers_processed 1812 AL_BUFFERS_PROCESSED, &buffers_processed
1796 ); 1813 );
1797 if((error = alGetError()) != AL_NO_ERROR) 1814 if((error = alGetError()) != AL_NO_ERROR)
1798 { 1815 {
1799 fprintf(stderr, "17cTesting Error with buffers_processed on Halt. (You may be seeing this because of a bad Apple OpenAL iOS 5.0 regression bug): %s", 1816 fprintf(stderr, "17cTesting Error with buffers_processed on Halt. (You may be seeing this because of a bad Apple OpenAL iOS 5.0 regression bug): %s", alGetString(error));
1800 alGetString(error)); 1817 ALmixer_SetError("Failed detecting still processed buffers: %s", alGetString(error) );
1801 ALmixer_SetError("Failed detecting still processed buffers: %s",
1802 alGetString(error) );
1803 /* This whole iOS 5.0 bug is so messed up that returning an error value probably isn't helpful. 1818 /* This whole iOS 5.0 bug is so messed up that returning an error value probably isn't helpful.
1804 retval = AL_FALSE; 1819 retval = AL_FALSE;
1805 */ 1820 */
1806 } 1821 }
1807 /* 1822 /*
1809 */ 1824 */
1810 /* Wow this is the bug that just keeps on sucking. There is an additional race condition bug where the unqueue may not actually work. 1825 /* Wow this is the bug that just keeps on sucking. There is an additional race condition bug where the unqueue may not actually work.
1811 * So I have to keep doing it until it does. 1826 * So I have to keep doing it until it does.
1812 * I hope this doesn't infinite loop. 1827 * I hope this doesn't infinite loop.
1813 */ 1828 */
1829 /* Disabling this print because we hit it way too much with the usleep(0) */
1830 /*
1814 if(0 != buffers_processed) 1831 if(0 != buffers_processed)
1815 { 1832 {
1816 fprintf(stderr, "Evil Apple OpenAL iOS 5.0 race condition. Buffers didn't actually unqueue. Repeating unqueue loop.\n"); 1833 fprintf(stderr, "Evil Apple OpenAL iOS 5.0 race condition. Buffers didn't actually unqueue. Repeating unqueue loop. %d, %d %d\n", ALmixer_GetTicks(), timeout_counter, ALmixer_GetTicks()-timeout_counter);
1817 } 1834 }
1818 } while(0 != buffers_processed); 1835 */
1836 } while(0 != buffers_processed && ( (ALmixer_GetTicks()-timeout_counter) < MAX_NUMBER_OF_TICKS_TO_WAIT_IN_WORKAROUND) );
1819 1837
1820 /* Avoid calling the normal cleanup because part of this bug seems to be triggered by alSourcei(source_id, AL_BUFFER, AL_NONE); */ 1838 /* Avoid calling the normal cleanup because part of this bug seems to be triggered by alSourcei(source_id, AL_BUFFER, AL_NONE); */
1821 return retval; 1839 return retval;
1822 } 1840 }
1823 1841
1824 #endif 1842 #endif
1825 #endif /* iOS 5.0 workaround */ 1843 #endif /* iOS 5.0 workaround */
1826 1844
1827 /* According to the spec, this is the best way to clear a source. 1845 /* According to the spec, this is the best way to clear a source.