BackgroundAudioPlayer «Воспроизведение», но не вызывает GetSampleAsync()

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

Когда дорожка начинает воспроизводиться, все проходит через первый вызов MediaStreamSource.GetSampleAsync(). Поскольку соединение нестабильно, если данных недостаточно, источник вызывает ReportGetSampleProgress(double) и возвращает результат, не сообщая о выборке. Это соответствует документации MSDN и примерам кода.

Любопытно, что дальнейших вызовов GetSampleAsync вообще нет! По мере того, как буферизация продолжается, источник продолжает ReportGetSampleProgress до тех пор, пока образец не будет готов, когда он вызывает ReportGetSampleProgress(1.0), чтобы указать полный буфер.

Я пробовал несколько подходов, в том числе:

  • ReportGetSampleCompleted после завершения буферизации; это не удается, потому что события загрузки приходят в произвольные потоки, и этот метод, очевидно, чувствителен как к вызывающему потоку, так и к тому, находится ли вызов GetSampleAsync в стеке; неправильные обстоятельства вызова приводят к ошибкам COM.
  • В точном состоянии ошибки остановите и запустите BackgroundAudioPlayer: это не приведет к перезапуску потоковой передачи.

Как я могу возобновить потоковую передачу после того, как первоначальная ошибка при чтении образца зависла?


person Ben    schedule 15.03.2013    source источник


Ответы (3)


ReportGetSampleCompleted как только данные будут доступны, является правильным подходом в этом сценарии.

Вам нужно будет отслеживать в своем MSS, нужно ли вам немедленно сообщать о любых новых данных выборки или ждать вызова GetSampleAsync.

Однако следите за тем, чтобы между различными участвующими потоками возможны сбои из-за состояния гонки.

person Paul Annetts    schedule 15.03.2013
comment
При каких обстоятельствах целесообразно вызывать ReportGetSampleCompleted? Это, кажется, суть проблемы - единственный раз, когда я знаю, что закончил буферизацию, я нахожусь в другом потоке и не могу безопасно его вызывать. - person Ben; 16.03.2013
comment
Безопасно звонить, если есть GetSampleAsync, которому вы не смогли предоставить образец. Может у вас тут другая проблема... - person Paul Annetts; 16.03.2013
comment
Вполне возможно, что есть еще одна проблема, но я положительно заметил, что отчет об образце из потока ThreadPool (с использованием того же пути кода чтения образца) приводит к исключению, маршалированному из ошибки COM; тот же код завершается успешно, как описано в документации, при вызове из вызова GetSampleAsync. Это как на устройстве, так и в эмуляторе. - person Ben; 16.03.2013
comment
Исключение маршалируется как NullReferenceException. Я проверил в отладчике, что ничто из того, что я передаю в ReportGetSampleCompleted, не является нулевым: образец создан, он имеет правильное описание потока, ключи атрибутов и т. д. Трассировка стека показывает, что классом генерирования является XcpImports, который просто сопоставляет HRESULT с предопределенными исключения. - person Ben; 16.03.2013
comment
@PaulAnnetts Я попробовал то, что вы предложили, и это действительно работает. Основная часть заключается в вызове ReportGetSampleCompleted после ReportGetSampleProgress(1.0); Но PlayState никогда не переходит к буферизации. - person Alex Sorokoletov; 11.01.2014

Как оказалось, решение состоит в том, чтобы разорвать контракт, предложенный именем GetSampleAsync, и заблокировать этот метод, когда в буфере недостаточно данных. Затем потоковые обратные вызовы могут пульсировать заблокированный объект, и можно повторить попытку чтения выборки. Что-то вроде этого хорошо работает:

private void OnMoreDataDownloaded(object sender, EventArgs e)
{
    // We're on an arbitrary thread, so instead of reporting
    // a sample here we should just pulse.
    lock (buffering_lock) {
        is_buffering = false;
        Monitor.Pulse(buffering_lock);
    }
}

protected override void GetSampleAsync()
{
    while (we_need_more_data) {
        lock (buffering_lock) {
            is_buffering = true;
            while (is_buffering) {
                Monitor.Wait(buffering_lock);
            }
    }

    // code code code
    ReportGetSampleCompleted(sample);
}

Казалось бы, блокировка в асинхронном методе может быть неразумной, но опыт запуска этого кода на устройстве говорит об обратном. По http://msdn.microsoft.com/en-us/library/system.windows.media.mediastreamsource.getsampleasync(v=vs.95).aspx, блокировка может помешать чтению других потоков. Как потоковое музыкальное приложение, мы всегда обслуживаем только один поток за раз, поэтому в этом случае кажется, что у нас все в порядке.

Хотелось бы, чтобы я знал здесь общее решение, потому что это явно обман.

person Ben    schedule 17.03.2013
comment
Прошло некоторое время с тех пор, как я просматривал этот код (сейчас на другой работе), но, насколько я помню, приложение входило и выходило из состояния буферизации без проблем. - person Ben; 11.01.2014
comment
ааааа, мой не входит в эти состояния. Спасибо за информацию, @Ben - person Alex Sorokoletov; 11.01.2014

Если у вас нет доступных данных, заполните буфер тишиной и сообщите об этом. Это даст вам время для поступления реальных данных.

Вы хотите центрировать данные в середине диапазона PCM на ваших данных тишины, иначе вы получите щелчок, когда отключитесь.

MemoryStream stream = new MemoryStream();

byte[] silenceBuffer = BitConverter.GetBytes( (ushort)(short.MaxValue) );
for(int i=0; i < 1000; i++ )
    stream.Write( silenceBuffer, 0, silenceBuffer.Length );

Удачи.

person Brian    schedule 30.05.2013