Android с Nexus 6, как избежать снижения приоритета аудиопотока OpenSL, связанного с фокусом приложения?

Я столкнулся со странной проблемой при попытке реализовать воспроизведение потокового аудио с малой задержкой на Nexus 6 под управлением Android 6.0.1 с использованием OpenSL ES.

Моя первоначальная попытка, похоже, страдала от проблем с голоданием, поэтому я добавил несколько базовых тестов времени в функцию обратного вызова завершения буфера. Я обнаружил, что звук воспроизводится нормально, если я постоянно нажимаю на экран, когда мое приложение открыто, но если я оставляю его в покое на несколько секунд, обратный вызов начинает занимать намного больше времени. Я могу воспроизвести это поведение последовательно. Несколько замечаний:

  • "несколько секунд" ~= 3-5 секунд, недостаточно долго, чтобы вызвать смену экрана
  • Активность моего приложения устанавливает FLAG_KEEP_SCREEN_ON, поэтому в любом случае не должно происходить никаких изменений экрана.
  • Я не предпринял никаких действий, чтобы попытаться увеличить приоритет потока аудио обратного вызова, так как у меня сложилось впечатление, что Android уже резервирует высокий приоритет для этих потоков.
  • Такое поведение наблюдается на моем Nexus 6 (Android 6.0.1), но не на Galaxy S6, который у меня также есть (Android 5.1.1).

Симптомы, которые я вижу, действительно выглядят так, будто ОС сбрасывает приоритет аудиопотока после нескольких секунд отсутствия взаимодействия с телефоном. Это правильно? Можно ли как-то избежать такого поведения?


person Jack O'Reilly    schedule 01.03.2016    source источник
comment
Также не возникает на Nexus 5 под управлением Android 4.4.2.   -  person Jack O'Reilly    schedule 03.03.2016
comment
Регистрация приоритета потока во время воспроизведения, по-видимому, показывает, что приоритет потока на самом деле не меняется при касании экрана. Может быть, событие касания вызывает прерывание, которое нарушает то, к чему, скорее всего, вернется планировщик?   -  person Jack O'Reilly    schedule 05.03.2016
comment
У меня такая же проблема в моих приложениях. Пока я взаимодействую с экраном, все работает гладко, как и должно быть. Как только я перестаю касаться экрана, через несколько секунд (около 3-5 секунд) начинаются массовые выпадения. Когда я снова начинаю взаимодействовать с экраном, он возвращается к плавной работе. Не имеет значения, делает ли взаимодействие с экраном что-то реальное, или срабатывает ли палец с помощью определенного элемента управления/кнопки. Как только палец двигается по экрану, звук воспроизводится плавно. Это не происходит на Android 5.x, это только началось с 6.0, и это также имеет место на Android N.   -  person gal    schedule 18.03.2016
comment
Это определенно не проблема производительности в приложении, я провел тесты с одной предварительно рассчитанной синусоидой, поведение такое же. Я использовал Nexus 6 для тестов. Я также заметил, что поведение точно такое же, когда приложение уходит в фоновый режим по-настоящему.   -  person gal    schedule 18.03.2016


Ответы (3)


Просматривая последнюю аудиопрезентацию Google I/O 2016, я наконец нашел причину и (уродливое) решение этой проблемы.

Просто посмотрите примерно одну минуту этого клипа на You Tube (начиная с 8:56): https://youtu.be/F2ZDp-eNrh4?t=8m56s

Объясняется, почему это происходит и как от этого избавиться.

На самом деле, Android замедляет работу процессора после нескольких секунд бездействия сенсорного экрана, чтобы уменьшить расход заряда батареи. Парень в видео обещает скорое решение этой проблемы, но на данный момент единственный способ избавиться от этого — отправить поддельные касания (это официальная рекомендация).

Instrumentation instr = new Instrumentation();
instr.sendKeyDownUpSync(KeyEvent.KEYCODE_BACKSLASH); // or whatever event you prefer

Повторяйте это с таймером каждые 1,5 секунды, и проблема исчезнет.

Я знаю, что это уродливый хак, и он может иметь уродливые побочные эффекты, с которыми нужно справиться. Но пока это просто единственное решение.

Обновление: что касается вашего последнего комментария... вот мое решение. Я использую обычный MotionEvent.ACTION_DOWN за пределами экрана. Все остальное нежелательным образом мешало пользовательскому интерфейсу. Чтобы избежать SecurityException, инициализируйте таймер в обработчике onStart() основного действия и завершите его в обработчике onStop(). По-прежнему бывают ситуации, когда приложение переходит в фоновый режим (в зависимости от загрузки ЦП), в которых вы можете столкнуться с SecurityException, поэтому вы должны окружить фальшивый сенсорный вызов блоком try catch.

Обратите внимание, что я использую свою собственную структуру таймера, поэтому вам нужно преобразовать код, чтобы использовать любой таймер, который вы хотите использовать.

Кроме того, я пока не могу гарантировать, что код на 100% пуленепробиваем. В моих приложениях применен этот хак, но в настоящее время они находятся в состоянии бета-тестирования, поэтому я не могу дать вам никаких гарантий, что это работает правильно на всех устройствах и версиях Android.

Timer fakeTouchTimer = null;
Instrumentation instr;
void initFakeTouchTimer()
{
    if (this.fakeTouchTimer != null)
    {
        if (this.instr == null)
        {
            this.instr = new Instrumentation();
        }
        this.fakeTouchTimer.restart();
    }
    else
    {
        if (this.instr == null)
        {
            this.instr = new Instrumentation();
        }
        this.fakeTouchTimer = new Timer(1500, Thread.MIN_PRIORITY, new TimerTask()
        {
            @Override
            public void execute()
            {
                if (instr != null && fakeTouchTimer != null && hasWindowFocus())
                {
                    try
                    {
                        long downTime = SystemClock.uptimeMillis();

                        MotionEvent event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, -100, -100, 0);
                        instr.sendPointerSync(event);
                        event.recycle();
                    }
                    catch (Exception e)
                    {
                    }
                }
            }
        }, true/*isInfinite*/);
    }
}
void killFakeTouchTimer()
{
    if (this.fakeTouchTimer != null)
    {
        this.fakeTouchTimer.interupt();
        this.fakeTouchTimer = null;
        this.instr = null;
    }
}

@Override
protected void onStop()
{
    killFakeTouchTimer();
    super.onStop();

    .....
}

@Override
protected void onStart()
{
    initFakeTouchTimer();
    super.onStart();

    .....
}
person gal    schedule 23.05.2016
comment
Спасибо за ответ; это определенно похоже на проблему, к которой я пытался добраться до сути. Я обновил свое приложение с помощью предложенного ими обходного пути, но, к сожалению, он кажется более хакерским, чем ожидалось. В частности, он может аварийно завершать работу с исключением безопасности, если предупреждение о громкости появляется во время имитируемого события касания (без программных событий касания между приложениями). У вас была возможность реализовать более надежную версию этого самостоятельно? - person Jack O'Reilly; 23.05.2016
comment
Вот что я имел в виду под уродливыми побочными эффектами. SecurityException является лишь одним из них. Я обновил свой ответ своим окончательным решением. - person gal; 24.05.2016
comment
Я только что заметил, что до сих пор не обращал внимания на такие ситуации, как предупреждение о громкости (или другие ситуации того же характера). Я обновил решение, чтобы охватить все эти ситуации. - person gal; 24.05.2016
comment
Попробовал это, поддельные события движения будут мешать реальным прикосновениям пользователей. Отправка KEYCODE_BACKSLASH вместо этого выбирает первое представление в макете. :/ - person athos; 01.08.2016
comment
Да, KEYCODE_BACKSLASH мешает пользовательскому интерфейсу, это правильно. Но посмотрите на окончательное решение. Он запускает только MotionEvent.ACTION_DOWN, -100, -100, 0), что означает событие ACTION_DOWN (не ACTION_UP) за пределами экрана (-100, -100). Эти события не могут вмешиваться, потому что ОС Android их отбрасывает. Но они держат процесс на максимальной скорости. Также применяйте исправление только для устройств Nexus 6 (shamu), работающих на Android SDK уровня 23 или выше, поскольку проблема возникает только в этой ситуации. Исправление работает во всех наших приложениях уже более двух месяцев, и проблем нет вообще. - person gal; 03.08.2016
comment
Спасибо за ответ! У меня была эта проблема в течение многих лет, и разработчики, похоже, не заботились об этом. Наконец-то они это заметили :-) Самое забавное, что они выпустили поддержку звука с низкой задержкой в ​​Android 4.1/4.2, а через год представили агрессивную функцию энергосбережения, которая более или менее отменила поддержку звука с низкой задержкой. По крайней мере, для меня это генерация звука на основе ввода с миди-клавиатуры. - person Floaf; 07.11.2016

Хорошо известно, что аудиоконвейер в Android 6 был полностью переписан. Хотя в большинстве случаев это решило проблемы, связанные с задержкой, не исключено, что оно породило ряд нежелательных побочных эффектов, как это обычно бывает с такими крупномасштабными изменениями.

Хотя ваша проблема не кажется распространенной, есть несколько вещей, которые вы можете попробовать:

  • Увеличить приоритет потока аудио. Приоритет по умолчанию для аудиопотоков в Android — -16, с максимальным значением -20, обычно доступным только для системных служб. Хотя вы не можете присвоить это значение своему аудиопотоку, вы можете присвоить следующую лучшую вещь: -19, используя флаг ANDROID_PRIORITY_URGENT_AUDIO при установке приоритета потока.

  • Увеличьте количество буферов, чтобы предотвратить любое дрожание или задержку (вы даже можете увеличить их до 16). Однако на некоторых устройствах обратный вызов для заполнения нового буфера не всегда вызывается, когда должен.

  • В этом сообщении SO есть несколько предложений по улучшению задержки звука на Android . Особый интерес представляют пункты 3, 4 и 5 в принятом ответе.

  • Проверьте, поддерживает ли текущая система Android низкую задержку, запросив hasSystemFeature(FEATURE_AUDIO_LOW_LATENCY) или hasSystemFeature(FEATURE_AUDIO_PRO).


Кроме того, эта научная статья обсуждает стратегии решения проблем, связанных с задержкой звука в Android/OpenSL, включая подходы, связанные с буфером и интервалом обратного вызова.

person Community    schedule 07.03.2016
comment
Спасибо за ответ. К сожалению, я уже прочитал все эти (очень полезные) ресурсы и применил предложенные там методы. Я использую собственную частоту дискретизации устройства и собственный размер буфера устройства на устройстве, которое утверждает, что поддерживает аудио с малой задержкой (на самом деле, я полагаю, что Nexus 6 работает с наименьшим собственным буфером — 192 кадра — 4 мс при 48 кГц). Увеличение номера буфера не помогло по причине, описанной в статье на сайтеheatvst.com, на которую вы ссылаетесь. Мой вопрос конкретно о том, почему взаимодействие с пользовательским интерфейсом влияет на воспроизведение звука. - person Jack O'Reilly; 07.03.2016
comment
Установлены ли у вас какие-либо сторонние приложения, которые могут получить фокус в фоновом режиме через пару секунд бездействия? - person ; 08.03.2016
comment
Как сделать аудио поток ANDROID_PRIORITY_URGENT_AUDIO приоритетным? - person Matthew Mitchell; 21.11.2016

Принудительная передискретизация до исходной частоты дискретизации устройства на Android 6.

Используйте собственную частоту дискретизации устройства 48 000. Например:

SLDataFormat_PCM формат данных;

dataFormat.samplesPerSec = 48000;

person Kenny Kuang    schedule 31.03.2017