2017-11-06 90 views
11

Problem

szukam do ekstraktu próbki z dokładnością zakresy LPCM audio z ścieżek audio w plikach wideo. Obecnie staram się to osiągnąć, używając AVAssetReaderTrackOutput przeciwko AVAssetTrack dostarczonego z czytania AVURLAsset.Próbka dokładne wydobycie kawałkami audio za pomocą AVFoundation

Pomimo przygotowania i zapewnienia, że ​​zasób zostanie zainicjowany przy użyciu zestawu AVURLAssetPreferPreciseDurationAndTimingKey, ustawionego na YES, próba znalezienia dokładnej pozycji w obrębie danego elementu wydaje się być niedokładna.

NSDictionary *options = @{ AVURLAssetPreferPreciseDurationAndTimingKey : @(YES) }; 
_asset = [[AVURLAsset alloc] initWithURL:fileURL options:options]; 

Przejawia się to np. strumienie AAC zakodowane z różną przepływnością. Chociaż wiem, że strumienie audio VBR przedstawiają koszty ogólne w dokładnym poszukiwaniu, jestem skłonny zapłacić, pod warunkiem, że dostarczyłem dokładne próbki.

Podczas korzystania np. Rozszerzone usługi plików audio i interfejsy API ExtAudioFileRef pozwalają uzyskać precyzyjne wyszukiwanie i ekstrakcję dźwięku. Podobnie jest z AVAudioFile, ponieważ jest on zbudowany na bazie ExtAudioFileRef.

Problem jest jednak to, chciałbym również, aby wyodrębnić audio z pojemników mediów, że tylko audio-file API odrzucają, lecz obsługiwane AVFoundation poprzez AVURLAsset.

Sposób

Próbkę dokładny przedział czasu ekstrakcji jest zdefiniowana CMTime i CMTimeRange i ustawiony na AVAssetReaderTrackOutput. Próbki są następnie ekstrahowane iteracyjnie.

-(NSData *)readFromFrame:(SInt64)startFrame 
     requestedFrameCount:(UInt32)frameCount 
{ 
    NSUInteger expectedByteCount = frameCount * _bytesPerFrame; 
    NSMutableData *data = [NSMutableData dataWithCapacity:expectedByteCount]; 

    // 
    // Configure Output 
    // 

    NSDictionary *settings = @{ AVFormatIDKey    : @(kAudioFormatLinearPCM), 
           AVLinearPCMIsNonInterleaved : @(NO), 
           AVLinearPCMIsBigEndianKey : @(NO), 
           AVLinearPCMIsFloatKey  : @(YES), 
           AVLinearPCMBitDepthKey  : @(32), 
           AVNumberOfChannelsKey  : @(2) }; 

    AVAssetReaderOutput *output = [[AVAssetReaderTrackOutput alloc] initWithTrack:_track outputSettings:settings]; 

    CMTime startTime = CMTimeMake(startFrame, _sampleRate); 
    CMTime durationTime = CMTimeMake(frameCount, _sampleRate); 
    CMTimeRange range = CMTimeRangeMake(startTime, durationTime); 

    // 
    // Configure Reader 
    // 

    NSError *error = nil; 
    AVAssetReader *reader = [[AVAssetReader alloc] initWithAsset:_asset error:&error]; 

    if(!reader) 
    { 
     fprintf(stderr, "avf : failed to initialize reader\n"); 
     fprintf(stderr, "avf : %s\n%s\n", error.localizedDescription.UTF8String, error.localizedFailureReason.UTF8String); 
     exit(EXIT_FAILURE); 
    } 

    [reader addOutput:output]; 
    [reader setTimeRange:range]; 
    BOOL startOK = [reader startReading]; 

    NSAssert(startOK && reader.status == AVAssetReaderStatusReading, @"Ensure we've started reading."); 

    NSAssert(_asset.providesPreciseDurationAndTiming, @"We expect the asset to provide accurate timing."); 

    // 
    // Start reading samples 
    // 

    CMSampleBufferRef sample = NULL; 
    while((sample = [output copyNextSampleBuffer])) 
    { 
     CMTime presentationTime = CMSampleBufferGetPresentationTimeStamp(sample); 
     if(data.length == 0) 
     { 
      // First read - we should be at the expected presentation time requested. 
      int32_t comparisonResult = CMTimeCompare(presentationTime, startTime); 
      NSAssert(comparisonResult == 0, @"We expect sample accurate seeking"); 
     } 

     CMBlockBufferRef buffer = CMSampleBufferGetDataBuffer(sample); 

     if(!buffer) 
     { 
      fprintf(stderr, "avf : failed to obtain buffer"); 
      exit(EXIT_FAILURE); 
     } 

     size_t lengthAtOffset = 0; 
     size_t totalLength = 0; 
     char *bufferData = NULL; 

     if(CMBlockBufferGetDataPointer(buffer, 0, &lengthAtOffset, &totalLength, &bufferData) != kCMBlockBufferNoErr) 
     { 
      fprintf(stderr, "avf : failed to get sample\n"); 
      exit(EXIT_FAILURE); 
     } 

     if(bufferData && lengthAtOffset) 
     { 
      [data appendBytes:bufferData length:lengthAtOffset]; 
     } 

     CFRelease(sample); 
    } 

    NSAssert(reader.status == AVAssetReaderStatusCompleted, @"Completed reading"); 

    [output release]; 
    [reader release]; 

    return [NSData dataWithData:data]; 
} 

Uwagi

Czas prezentacji że CMSampleBufferGetPresentationTimeStamp daje mi wydaje się, aby dopasować to, co poszukiwane - ale jak się wydaje niedokładne, to nie ma szans, aby skorygować i wyrównać próbki odzyskać.

Wszelkie uwagi na temat tego, jak to zrobić?

Czy istnieje sposób dostosowania AVAssetTrack do wykorzystania przez AVAudioFile lub ExtAudioFile?

Czy można uzyskać dostęp do ścieżki audio przez AudioFileOpenWithCallbacks?

Czy można uzyskać w strumieniu audio z kontenera wideo w inny sposób w systemie MacOS?

+1

Należy zauważyć, że czasami AVFoundation dostarcza mniej niż próbki są niezbędne do wystarczająco spełniają wymagania durationTime' '. To nie stanowi problemu dla np. mieć 'durationTime' z' kCMTimePositiveInfinity' i po prostu odczytać wystarczającą liczbę próbek w razie potrzeby ... to początkowe wyszukiwanie jest problematyczne. – Dan

Odpowiedz

3

Jedną z procedur, która działa, jest użycie AVAssetReader, aby odczytać skompresowany plik AV w połączeniu z AVAssetWriter, aby zapisać nowy nieprzetworzony plik LPCM próbek audio. Następnie można szybko indeksować ten nowy plik PCM (lub tablicę odwzorowaną w pamięci, jeśli jest to konieczne), aby wyodrębnić dokładne zakresy dokładności próbkowania, bez powodowania anomalii wielkości dekodowania VBR w pakietach lub w zależności od algorytmów CMTimeStamp iOS znajdujących się poza kontrolą użytkownika.

To może nie być najbardziej czasowa lub wydajna pamięć, ale działa.

+1

To by na pewno działało - jednak bardzo chciałbym uniknąć pośredniego kompletnego wyjścia całej ścieżki źródłowej audio do pamięci/dysku. Korzystanie z np. 'AVAssetExportSession' i zapisanie ścieżki audio bez ponownego kodowania na dysk (pass-through), a następnie odczytanie, że używanie API tylko plików audio działa, ale jest kosztownym krokiem. – Dan

0

napisałem kolejną odpowiedź, w którym błędnie twierdził AVAssetReader/AVAssetReaderTrackOutput nie zrobić przykładową dokładne poszukiwania, robią, ale wygląda na złamany, gdy ścieżka dźwiękowa jest osadzony wewnątrz pliku filmowego, więc znalazłeś błąd. Gratulacje!

Ścieżka dźwiękowa porzucona przy przejściu przez AVAssetExportSession, jak wspomniano w komentarzu do odpowiedzi @ hotpaw2, działa dobrze, nawet jeśli szukasz granic poza pakietem (w międzyczasie poszukiwałeś granic pakietów, plik połączony ma 1024 klatki na pakiet - szukanie granic pakietów, twoje różnice nie są już zerowe, ale są bardzo, bardzo małe/niesłyszalne).

nie znalazłem obejście, więc rozważyć dumping sprężonego utwór. Czy to jest kosztowne? Jeśli naprawdę nie chcesz tego zrobić, możesz dekodować surowe pakiety samodzielnie, przekazując niloutputSettings: do swojego AVAssetReaderOutput i uruchamiając jego wyjście przez AudioQueue lub (najlepiej?) I AudioConverter, aby uzyskać LPCM.

NB w tym ostatnim przypadku, będzie trzeba obsłużyć zaokrąglając w górę do granic pakietowych podczas poszukiwania.