iOS Stream Audio с одного устройства iOS на другое - PullRequest
17 голосов
/ 02 декабря 2011

Я получаю песню из библиотеки iTunes устройства и помещаю ее в AVAsset:

- (void)mediaPicker: (MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection
{
    NSArray *arr = mediaItemCollection.items;

    MPMediaItem *song = [arr objectAtIndex:0];

    NSData *songData = [NSData dataWithContentsOfURL:[song valueForProperty:MPMediaItemPropertyAssetURL]];
}

Тогда у меня есть метод Game Center для получения данных:

- (void)match:(GKMatch *)match didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID

У меня много проблем с выяснением, как отправить этот AVAsset через GameCenter и затем воспроизвести его на принимающем устройстве.

Я прочитал: http://developer.apple.com/library/ios/#documentation/MusicAudio/Reference/AudioStreamReference/Reference/reference.html#//apple_ref/doc/uid/TP40006162

http://developer.apple.com/library/ios/#documentation/AudioVideo/Conceptual/MultimediaPG/UsingAudio/UsingAudio.html#//apple_ref/doc/uid/TP40009767-CH2-SW5

http://developer.apple.com/library/mac/#documentation/AVFoundation/Reference/AVAudioPlayerClassReference/Reference/Reference.html

http://developer.apple.com/library/mac/#documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/Introduction/Introduction.html

Я просто потерян. Информационная перегрузка.

Я реализовал код аудио потока «Какао с любовью», но не могу понять, как взять NSData, который я получаю через GameCenter, и вставить его в свой код. http://cocoawithlove.com/2008/09/streaming-and-playing-live-mp3-stream.html

Может кто-нибудь помочь мне разобраться? Итак, снова часть, с которой мне нужна помощь - это просто разбить данные песни на пакеты (или как бы это ни работало), затем перебрать эти пакеты и отправить их через gamekit, а затем проанализировать эти данные AS, которые поступают на принимающее устройство, как PLAY it AS это входит.

Ответы [ 2 ]

31 голосов
/ 13 декабря 2011

API, на который вам нужно обратить внимание: "Audio Queue Services" .

Правильно, здесь приведен общий обзор того, что вам нужно сделать.

При воспроизведении аудио вы устанавливаете очередь или службу. Эта очередь будет запрашивать некоторые аудиоданные. Затем, когда он проиграл все это, он попросит еще немного. Это продолжается до тех пор, пока вы не остановите очередь или не закончите воспроизведение данных.

Двумя основными API нижнего уровня в iOS являются Audio Unit и Audio Queue. Под более низким уровнем я подразумеваю API, который немного более грубый, чем сказать «просто воспроизвести этот mp3» или что-то в этом роде.

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

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

Пример проекта, который я бы рекомендовал внимательно изучить, - SpeakHere . В частности посмотрите на классы SpeakHereController.mm и AQPlayer.mm.

Контроллер обрабатывает такие вещи, как запуск и остановка AQPlayer. AQPlayer представляет AudioQueue. Посмотрите внимательно на AQPlayer::AQBufferCallback. Это метод обратного вызова, который вызывается, когда очереди требуется больше данных.

Вам необходимо убедиться, что настройки данных очереди и формат полученных данных точно совпадают. Проверяйте такие вещи, как количество каналов (моно или стерео?), Количество кадров, целые числа или числа с плавающей запятой и частота дискретизации. Если что-то не совпадает, вы либо получите EXC_BAD_ACCESS ошибок при прохождении через соответствующие буферы, либо получите белый шум, либо - в случае неправильной частоты дискретизации - звук, который звучит замедленно или ускорился.

Обратите внимание, что SpeakHere запускает две аудио-очереди; один для записи, а другой для воспроизведения. Все аудио вещи работают с использованием буферов данных. Таким образом, вы всегда передаете указатели на буферы. Так, например, во время воспроизведения вы скажете буфер памяти, который имеет 20 секунд аудио. Возможно, каждую секунду ваш обратный вызов будет вызываться из очереди, по сути говоря, «дайте мне еще одну секунду данных, пожалуйста». Вы можете думать об этом как о воспроизводящей головке, которая перемещается по вашим данным, запрашивая дополнительную информацию.

Давайте посмотрим на это более подробно. В отличие от SpeakHere, вы будете работать с буферами памяти, а не записывать аудио во временный файл.

Обратите внимание, что если вы работаете с большими объемами данных на устройстве iOS, у вас не будет другого выбора, кроме как хранить большую часть данных на диске. Особенно, если пользователь может воспроизвести звук, перемотать его и т. Д., Вам нужно где-то все это хранить!

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

Во-первых, где-то для хранения данных, в AQPlayer.h:

void SetAudioBuffer(float *inAudioBuffer) { mMyAudioBuffer = inAudioBuffer; }

У вас уже есть эти данные в NSData объекте, так что вы можете просто передать указатель, возвращенный из вызова на [myData bytes].

Что предоставляет эти данные в аудио-очередь? Это метод обратного вызова, настроенный в AQPlayer:

void AQPlayer::AQBufferCallback(void *                  inUserData,
                            AudioQueueRef           inAQ,
                            AudioQueueBufferRef     inCompleteAQBuffer) 

Метод, который мы будем использовать для добавления части наших данных в аудио-очередь: AudioQueueEnqueueBuffer:

AudioQueueEnqueueBuffer(inAQ, inCompleteAQBuffer, 0, NULL);

inAQ - ссылка на очередь, полученную нашим обратным вызовом. inCompleteAQBuffer - указатель на буфер аудио-очереди.

Итак, как вы получаете ваши данные - то есть указатель, возвращаемый путем вызова метода bytes вашего объекта NSData - в буфер аудио-очереди inCompleteAQBuffer?

Использование memcpy:

memcpy(inCompleteAQBuffer->mAudioData, THIS->mMyAudioBuffer + (THIS->mMyPlayBufferPosition / sizeof(float)), numBytesToCopy);

Вам также необходимо установить размер буфера:

        inCompleteAQBuffer->mAudioDataByteSize =  numBytesToCopy;   

numBytesToCopy всегда будет одинаковым, , если только у вас просто не хватит данных.Например, если ваш буфер содержит 2 секунды аудиоданных и у вас есть 9 секунд для воспроизведения, то для первых четырех обратных вызовов вы пропустите значение 2 секунды.Для окончательного обратного вызова у вас останется только 1 секунда данных.numBytesToCopy должно отражать это.

    // Calculate how many bytes are remaining? It could be less than a normal buffer
    // size. For example, if the buffer size is 0.5 seconds and recording stopped
    // halfway through that. In which case, we copy across only the recorded bytes
    // and we don't enqueue any more buffers.
    SInt64 numRemainingBytes = THIS->mPlayBufferEndPosition - THIS->mPlayBufferPosition;

    SInt64 numBytesToCopy =  numRemainingBytes < THIS->mBufferByteSize ? numRemainingBytes : THIS->mBufferByteSize;

Наконец, мы продвигаем головку воспроизведения.В нашем обратном вызове мы дали очереди некоторые данные для воспроизведения.Что произойдет в следующий раз, когда мы получим обратный звонок?Мы не хотим снова предоставлять те же данные.Нет, если вы не делаете какой-то забавный диджейский цикл!

Таким образом, мы продвигаем голову, которая в основном является просто указателем на наш аудио-буфер.Указатель перемещается через буфер, как стрелка на записи:

    SELF->mPlayBufferPosition += numBytesToCopy;

Вот и все!Есть и другая логика, но вы можете получить ее, изучив полный метод обратного вызова в SpeakHere.

Пара моментов, которые я должен подчеркнуть.Во-первых, не просто скопируйте и вставьте мой код выше.Абсолютно убедитесь, что вы понимаете, что вы делаете.Несомненно, вы столкнетесь с проблемами, и вам нужно будет понять, что происходит.

Во-вторых, убедитесь, что аудиоформаты одинаковы, и еще лучше, чтобы вы понимали аудиоформат.Это описано в Руководстве по программированию Audio Queue Services в разделе Запись аудио .Посмотрите на Перечисление 2-8. Указание формата аудиоданных аудио-очереди .

Важно понимать, что у вас есть самая примитивная единица данных, целое число или число с плавающей точкой.Моно или стерео у вас есть один или два канала в кадре.Это определяет, сколько целых чисел или чисел с плавающей точкой в ​​этом кадре.Тогда у вас есть кадры на пакет (вероятно, 1).Ваша частота дискретизации определяет, сколько из этих пакетов у вас в секунду.

Все это описано в документации.Просто убедитесь, что все совпадает, иначе у вас будут довольно странные звуки!

Удачи!

0 голосов
/ 20 декабря 2011

Какова твоя цель? и как вы вызываете [match sendDataToAllPlayers: ...]? Он решает, как вернуть AVAsset из полученных данных.

Есть упомянутые шаги:

  1. получить NSData от mediaPicker для музыки
  2. создать AVAsset (вы действительно это делаете?)
  3. отправить в GKPlayers?
  4. получение NSData из игрового центра
  5. верните AVAsset, если вы dong 2.
  6. используйте AVPlayer: playerWithPlayItem, чтобы получить AVPlayer и играть

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...