AVAssetWriter Как записывать файлы m4a/mp3 с пониженной частотой дискретизации/сжатия

Я пытаюсь взять локальный файл m4a или mp3 и сжать/уменьшить выборку этого файла (для создания файла меньшего размера).

Первоначально я использовал AVAssetExportSession для экспорта AVAsset во временный каталог, но у меня не было никакого контроля над сжатием/суммированием (вы можете использовать только пресеты, какие из них, только форматы файлов .wav поддерживают ухудшение качества) .

Затем, следуя нескольким примерам здесь, на SO, я попытался использовать AVAssetReader/AVAssetWriter для предварительного формирования этого «экспорта».

Я создаю своего читателя/писателя как такового:

NSString *exportPath = [NSHomeDirectory() stringByAppendingPathComponent:@"out.m4a"];

NSURL *exportURL = [NSURL fileURLWithPath:outPath];

// reader
NSError *readerError = nil;
AVAssetReader *reader = [[AVAssetReader alloc] initWithAsset:asset
                                                       error:&readerError];

AVAssetTrack *track = [[asset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];    
AVAssetReaderTrackOutput *readerOutput = [[AVAssetReaderTrackOutput alloc] initWithTrack:track
                                                                          outputSettings:nil];
[reader addOutput:readerOutput];

// writer
NSError *writerError = nil;
AVAssetWriter *writer = [[AVAssetWriter alloc] initWithURL:exportURL
                                                  fileType:AVFileTypeAppleM4A
                                                     error:&writerError];

AudioChannelLayout channelLayout;
memset(&channelLayout, 0, sizeof(AudioChannelLayout));
channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;

// use different values to affect the downsampling/compression
NSDictionary *outputSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                [NSNumber numberWithInt: kAudioFormatMPEG4AAC], AVFormatIDKey,
                                [NSNumber numberWithFloat:44100.0], AVSampleRateKey,
                                [NSNumber numberWithInt:2], AVNumberOfChannelsKey,
                                [NSNumber numberWithInt:128000], AVEncoderBitRateKey,
                                [NSData dataWithBytes:&channelLayout length:sizeof(AudioChannelLayout)], AVChannelLayoutKey,
                                nil];

AVAssetWriterInput *writerInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio
                                                                 outputSettings:outputSettings];
[writerInput setExpectsMediaDataInRealTime:NO];
[writer addInput:writerInput];

И тогда я начинаю писать...

[writer startWriting];
[writer startSessionAtSourceTime:kCMTimeZero];

[reader startReading];
dispatch_queue_t mediaInputQueue = dispatch_queue_create("mediaInputQueue", NULL);
[writerInput requestMediaDataWhenReadyOnQueue:mediaInputQueue usingBlock:^{

    NSLog(@"Asset Writer ready : %d", writerInput.readyForMoreMediaData);
    while (writerInput.readyForMoreMediaData) {
        CMSampleBufferRef nextBuffer;
        if ([reader status] == AVAssetReaderStatusReading && (nextBuffer = [readerOutput copyNextSampleBuffer])) {
            if (nextBuffer) {
                NSLog(@"Adding buffer");
                [writerInput appendSampleBuffer:nextBuffer];
            }
        } else {
            [writerInput markAsFinished];

            switch ([reader status]) {
                case AVAssetReaderStatusReading:
                    break;
                case AVAssetReaderStatusFailed:
                    [writer cancelWriting];
                    break;
                case AVAssetReaderStatusCompleted:
                    NSLog(@"Writer completed");
                    [writer endSessionAtSourceTime:asset.duration];
                    [writer finishWriting];

                    NSData *data = [NSData dataWithContentsOfFile:exportPath];
                    NSLog(@"Data: %@", data);
                    break;
            }
            break;
        }
    }
}];

Когда я закончу писать, данные, которые я предположительно записал в exportURL, будут нулевыми, и писатель сообщит об успешном завершении. Любые идеи, что может пойти не так?

Обновление. Статус записи после вызова appendSampleBuffer: — AVAssetWriterStatusFailed, хотя статус чтения — успешный, и кажется, что файл прочитан целиком.


person Dfowj    schedule 24.10.2012    source источник
comment
Пытается ли он что-нибудь написать? Каково состояние считывателя при первой попытке добавить буфер сэмплов?   -  person BlueVoodoo    schedule 25.10.2012
comment
Ах-ха, это появляется на каждом шагу, когда вызывается [writerInput appendSampleBuffer:], статус записи равен 3, что, глядя на перечисление, выглядит как AVAssetWriterStatusFailed. Похоже, что ридер копирует все образцы буферов из моего ресурса - appendSampleBuffer вызывается много раз   -  person Dfowj    schedule 25.10.2012
comment
Можно добавить это, чтобы избежать утечки памяти: CMSampleBufferInvalidate(nextBuffer); CFRRelease (следующий буфер); следующий буфер = NULL;   -  person guozqzzu    schedule 28.10.2019


Ответы (1)


Я наткнулся на решение:

Использование NSHomeDirectory() в NSString *exportPath = [NSHomeDirectory() stringByAppendingPathComponent:@"out.m4a"] приводило к тому, что писатель не мог создать файл. Не совсем уверен, почему или что мне нужно сделать, чтобы это работало, но тем временем изменение NSHomeDirectiory() на NSTemporaryDirectory() решило мои проблемы.

person Dfowj    schedule 25.10.2012
comment
Вы не можете писать в домашний каталог в iOS. Временный каталог в порядке, а также каталог документов - person Oded Ben Dov; 28.03.2013