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).

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

3 ответа

Решение

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

Просто посмотрите примерно одну минуту этого ролика (начиная с 8m56s): 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();

    .....
}

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

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

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

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

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

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


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

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

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

SLDataFormat_PCM dataFormat;

dataFormat.samplesPerSec = 48000;

Другие вопросы по тегам