2016-10-04 1 views
11

У меня есть вопрос об использовании файла AVFoundation AVPlayer (вероятно, применим как к iOS, так и к macOS). Я пытаюсь воспроизвести аудио (несжатые wav) данные, которые поступают из канала, отличного от стандартного HTTP Live Streaming.AVPlayer загрузка AVAsset из файла, который добавляется одновременно внешним источником (для macOS и iOS)

Корпус:
Пакеты аудиоданных сжимаются в канале вместе с другими данными, с которыми приложение должно работать. Например, видео и аудио входят в один канал и разделяются заголовком.
После фильтрации я получаю аудиоданные и распаковываю их в WAV-формат (на данном этапе не содержит заголовков).
После того, как пакеты данных готовы (9600 байт для 24k, стерео 16-битный звук), они передаются экземпляру AVPlayer (AVAudioPlayer в соответствии с Apple не подходит для потоковой передачи звука).

Учитывая, что AVPlayer (элемент или объект) не загружается из памяти (нет initWithData: (NSData)) и требует URL-адреса HTTP Live Stream или URL-адреса файла, я создаю файл на диске (либо macOS, либо iOS) , добавьте WAV Headers и добавьте несжатые данные там.

Назад на AVPlayer, я создаю следующее:

AVURLAsset *audioAsset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:tempAudioFile] options:nil]; 
AVPlayerItem *audioItem = [[AVPlayerItem alloc] initWithAsset:audioAsset]; 
AVPlayer *audioPlayer = [[AVPlayer alloc] initWithPlayerItem:audioItem]; 

добавления KVOs, а затем попытаться начать воспроизведение:

[audioPlayer play]; 

Результатом является то, что звук играет в течение 1-2 секунд и затем останавливается (AVPlayerItemDidPlayToEndTimeNotification), а данные продолжают добавляться в файл. Поскольку все это в цикле, [воспроизведение аудиопользователя] запускается и приостанавливает (скорость == 0) несколько раз.

Вся концепция в упрощенном виде:

-(void)PlayAudioWithData:(NSData *data) //data in encoded format 
{ 
    NSData *decodedSound = [AudioDecoder DecodeData:data]; //decodes the data from the compressed format (Opus) to WAV 
    [Player CreateTemporaryFiles]; //This creates the temporary file by appending the header and waiting for input. 

    [Player SendDataToPlayer:decodedSound]; //this sends the decoded data to the Player to be stored to file. See below for appending. 

    Boolean prepared = [Player isPrepared]; //a check if AVPlayer, Item and Asset are initialized 
    if (!prepared)= [Player Prepare]; //creates the objects like above 
    Boolean playing = [Player isAudioPlaying]; //a check done on the AVPlayer if rate == 1 
    if (!playing) [Player startPlay]; //this is actually [audioPlayer play]; on AVPlayer Instance 
} 

-(void)SendDataToPlayer:(NSData *data) 
{ 
    //Two different methods here. First with NSFileHandle — not so sure about this though as it definitely locks the file. 
    //Initializations and deallocations happen elsewhere, just condensing code to give you an idea 
    NSFileHandle *audioFile = [NSFileHandle fileHandleForWritingAtPath:_tempAudioFile]; //happens else where 
    [audioFile seekToEndOfFile]; 
    [audioFile writeData:data]; 
    [audioFile closeFile]; //happens else where 

    //Second method is 
    NSOutputStream *audioFileStream = [NSOutputStream outputStreamWithURL:[NSURL fileURLWithPath:_tempStreamFile] append:YES]; 
    [audioFileStream open]; 
    [audioFileStream write:[data bytes] maxLength:data.length]; 
    [audioFileStream close]; 
} 

Оба NSFileHandle и NSOutputStream сделать полностью рабочие WAV файлы, сыгранные QuickTime, Itunes, VLC и т.д. Кроме того, если я обойти [Player SendDataToPlayer: decodedSound ] и иметь временный аудиофайл, предварительно загруженный стандартным WAV, он также воспроизводится нормально.

До сих пор есть две стороны: a) У меня есть аудиоданные, декомпрессированные и готовые к воспроизведению b) Я правильно сохраняю данные.

То, что я пытаюсь сделать, это send-write-read в строке. Это заставляет меня думать, что сохранение данных в файл, получение эксклюзивного доступа к файловому ресурсу и не позволяет AVPlayer продолжить воспроизведение.

Любой, кто имеет представление о том, как сохранить файл как NSFileHandle/NSOutputStream, так и AVPlayer?

Или еще лучше ... Есть ли AVPlayer initWithData? (hehe ...)

Любая помощь очень ценится! Спасибо заранее.

ответ

8

Вы можете использовать AVAssetResourceLoader к трубе собственные данные и метаданные в AVAsset, которые вы можете играть с AVPlayer, фактически делая [[AVPlayer alloc] initWithData:...]:

- (AVPlayer *)playerWithWavData:(NSData*)wavData { 
    self.strongDelegateReference = [[NSDataAssetResourceLoaderDelegate alloc] initWithData:wavData contentType:AVFileTypeWAVE]; 

    NSURL *url = [NSURL URLWithString:@"ns-data-scheme://"]; 
    AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:nil]; 

    // or some other queue != main queue 
    [asset.resourceLoader setDelegate:self.strongDelegateReference queue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; 

    AVPlayerItem *item = [[AVPlayerItem alloc] initWithAsset:asset]; 
    return [[AVPlayer alloc] initWithPlayerItem:item]; 
} 

, которые вы можете использовать, как так:

[self setupAudioSession]; 

NSURL *wavUrl = [[NSBundle mainBundle] URLForResource:@"foo" withExtension:@"wav"]; 
NSData *wavData = [NSData dataWithContentsOfURL:wavUrl]; 

self.player = [self playerWithWavData:wavData]; 

[self.player play]; 

Дело в том, что AVAssetResourceLoader очень мощный (unless you want to use AirPlay), поэтому вы, вероятно, можете лучше, чем подавать аудиоданные на AVPlayer в одном компе - вы можете передать его в делегат AVAssetResourceLoader, когда он станет доступен.

Вот простой «один комба» AVAssetResourceLoader делегат. Чтобы изменить его для потоковой передачи, должно быть достаточно установить более длинный contentLength, чем объем данных, которые у вас есть.

Заголовочный файл:

#import <Foundation/Foundation.h> 
#import <AVFoundation/AVFoundation.h> 

@interface NSDataAssetResourceLoaderDelegate : NSObject <AVAssetResourceLoaderDelegate> 

- (instancetype)initWithData:(NSData *)data contentType:(NSString *)contentType; 

@end 

файл Реализация:

@interface NSDataAssetResourceLoaderDelegate() 

@property (nonatomic) NSData *data; 
@property (nonatomic) NSString *contentType; 

@end 

@implementation NSDataAssetResourceLoaderDelegate 

- (instancetype)initWithData:(NSData *)data contentType:(NSString *)contentType { 
    if (self = [super init]) { 
     self.data = data; 
     self.contentType = contentType; 
    } 
    return self; 
} 

- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest { 
    AVAssetResourceLoadingContentInformationRequest* contentRequest = loadingRequest.contentInformationRequest; 

    // TODO: check that loadingRequest.request is actually our custom scheme   

    if (contentRequest) { 
     contentRequest.contentType = self.contentType; 
     contentRequest.contentLength = self.data.length; 
     contentRequest.byteRangeAccessSupported = YES; 
    } 

    AVAssetResourceLoadingDataRequest* dataRequest = loadingRequest.dataRequest; 

    if (dataRequest) { 
     // TODO: handle requestsAllDataToEndOfResource 
     NSRange range = NSMakeRange((NSUInteger)dataRequest.requestedOffset, (NSUInteger)dataRequest.requestedLength); 
     [dataRequest respondWithData:[self.data subdataWithRange:range]]; 
     [loadingRequest finishLoading]; 
    } 

    return YES; 
} 

@end 
+0

Два вопроса: 1) Вы загружаете данные из WAV файла в связке. Вы используете это только для источника данных, или я должен использовать wav-файл, в который я добавляю данные? (Примечание: у меня уже есть данные в памяти) и 2) Это происходит в цикле, так как новые данные поступают непрерывно. Мне нужно выделить AVPlayer или я должен где-то освободиться? – Pericles

+0

1) Я использую wav-файл, чтобы иметь некоторые исходные данные. 2) У меня недостаточно информации, чтобы сказать, когда вы должны освободить свой «AVPlayer». Что касается файла, к которому вы добавляете - я не знаю, если это необходимо, но вы можете либо передать делегата загрузчика из файла, либо непосредственно из распакованных данных в памяти. Но 'playerWithWavData' может быть не лучшим образом. Я только дал этот пример, потому что это то, о чем вы просили. –

Смежные вопросы