'Media Format' for '.caf' file in Amazon Transcribe - ios

I have a React Native (Expo) app which captures audio using the expo-av library.
It then uploads the audio file to Amazon S3, and then Transcribes that in Amazon Transcribe.
For Android , i save the audio as a '.m4a' file, and call the Amazon Transcribe API as :
transcribe_client.start_transcription_job(TranscriptionJobName = job_name,
Media={'MediaFileUri' : file_uri},
MediaFormat='mp4',
LanguageCode='en-US')
What should the 'MediaFormat' be for upload from an iOS device, which will typically be a '.caf' file ?
Amazon Transcribe only allows these media formats
MP3, MP4, WAV, FLAC, AMR, OGG, and WebM

Possible solutions:
Create an API wich does the conversion for you.
You can easly create one using for example the FFMPEG python library.
Use an already made API.
By using the cloudconvert API you can convert the file with ease, but only if you pay for it.
Use an different library to record the IOS audio.
There's an module called react-native-record-audio-ios wich is made entirely for IOS and record audio in .caf, .m4a, and .wav.
Use the LAME api to convert it.
As said here, you can convert a .caf file into a .mp3 one by probably creating a native module wich would run this:
FILE *pcm = fopen("file.caf", "rb");
FILE *mp3 = fopen("file.mp3", "wb");
const int PCM_SIZE = 8192;
const int MP3_SIZE = 8192;
short int pcm_buffer[PCM_SIZE*2];
unsigned char mp3_buffer[MP3_SIZE];
lame_t lame = lame_init();
lame_set_in_samplerate(lame, 44100);
lame_set_VBR(lame, vbr_default);
lame_init_params(lame);
do {
read = fread(pcm_buffer, 2*sizeof(short int), PCM_SIZE, pcm);
if (read == 0)
write = lame_encode_flush(lame, mp3_buffer, MP3_SIZE);
else
write = lame_encode_buffer_interleaved(lame, pcm_buffer, read, mp3_buffer, MP3_SIZE);
fwrite(mp3_buffer, write, 1, mp3);
} while (read != 0);
lame_close(lame);
fclose(mp3);
fclose(pcm);
Creating an native module who runs this objective-c code:
-(void) convertToWav
{
// set up an AVAssetReader to read from the iPod Library
NSString *cafFilePath=[[NSBundle mainBundle]pathForResource:#"test" ofType:#"caf"];
NSURL *assetURL = [NSURL fileURLWithPath:cafFilePath];
AVURLAsset *songAsset = [AVURLAsset URLAssetWithURL:assetURL options:nil];
NSError *assetError = nil;
AVAssetReader *assetReader = [AVAssetReader assetReaderWithAsset:songAsset
error:&assetError]
;
if (assetError) {
NSLog (#"error: %#", assetError);
return;
}
AVAssetReaderOutput *assetReaderOutput = [AVAssetReaderAudioMixOutput
assetReaderAudioMixOutputWithAudioTracks:songAsset.tracks
audioSettings: nil];
if (! [assetReader canAddOutput: assetReaderOutput]) {
NSLog (#"can't add reader output... die!");
return;
}
[assetReader addOutput: assetReaderOutput];
NSString *title = #"MyRec";
NSArray *docDirs = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [docDirs objectAtIndex: 0];
NSString *wavFilePath = [[docDir stringByAppendingPathComponent :title]
stringByAppendingPathExtension:#"wav"];
if ([[NSFileManager defaultManager] fileExistsAtPath:wavFilePath])
{
[[NSFileManager defaultManager] removeItemAtPath:wavFilePath error:nil];
}
NSURL *exportURL = [NSURL fileURLWithPath:wavFilePath];
AVAssetWriter *assetWriter = [AVAssetWriter assetWriterWithURL:exportURL
fileType:AVFileTypeWAVE
error:&assetError];
if (assetError)
{
NSLog (#"error: %#", assetError);
return;
}
AudioChannelLayout channelLayout;
memset(&channelLayout, 0, sizeof(AudioChannelLayout));
channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
NSDictionary *outputSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:kAudioFormatLinearPCM], AVFormatIDKey,
[NSNumber numberWithFloat:44100.0], AVSampleRateKey,
[NSNumber numberWithInt:2], AVNumberOfChannelsKey,
[NSData dataWithBytes:&channelLayout length:sizeof(AudioChannelLayout)], AVChannelLayoutKey,
[NSNumber numberWithInt:16], AVLinearPCMBitDepthKey,
[NSNumber numberWithBool:NO], AVLinearPCMIsNonInterleaved,
[NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey,
[NSNumber numberWithBool:NO], AVLinearPCMIsBigEndianKey,
nil];
AVAssetWriterInput *assetWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio
outputSettings:outputSettings];
if ([assetWriter canAddInput:assetWriterInput])
{
[assetWriter addInput:assetWriterInput];
}
else
{
NSLog (#"can't add asset writer input... die!");
return;
}
assetWriterInput.expectsMediaDataInRealTime = NO;
[assetWriter startWriting];
[assetReader startReading];
AVAssetTrack *soundTrack = [songAsset.tracks objectAtIndex:0];
CMTime startTime = CMTimeMake (0, soundTrack.naturalTimeScale);
[assetWriter startSessionAtSourceTime: startTime];
__block UInt64 convertedByteCount = 0;
dispatch_queue_t mediaInputQueue = dispatch_queue_create("mediaInputQueue", NULL);
[assetWriterInput requestMediaDataWhenReadyOnQueue:mediaInputQueue
usingBlock: ^
{
while (assetWriterInput.readyForMoreMediaData)
{
CMSampleBufferRef nextBuffer = [assetReaderOutput copyNextSampleBuffer];
if (nextBuffer)
{
// append buffer
[assetWriterInput appendSampleBuffer: nextBuffer];
convertedByteCount += CMSampleBufferGetTotalSampleSize (nextBuffer);
CMTime progressTime = CMSampleBufferGetPresentationTimeStamp(nextBuffer);
CMTime sampleDuration = CMSampleBufferGetDuration(nextBuffer);
if (CMTIME_IS_NUMERIC(sampleDuration))
progressTime= CMTimeAdd(progressTime, sampleDuration);
float dProgress= CMTimeGetSeconds(progressTime) / CMTimeGetSeconds(songAsset.duration);
NSLog(#"%f",dProgress);
}
else
{
[assetWriterInput markAsFinished];
// [assetWriter finishWriting];
[assetReader cancelReading];
}
}
}];
}
But, as said here:
Since the iPhone shouldn't really be used for processor intensive things such as audio conversion.
So i recommend you the third solution, because it's easier and doesn't look like an intensive task for the Iphone processor.

Related

ios - Convert video's audio to AAC

I'm trying to encode any audio format to AAC format, with 44100Hz sample rate.
So basically : input (mp3, aac? etc, any sample rate) -> AAC (44100Hz)
The source audio comes from a video (mp4), but I can extract it to m4a (AAC). The thing is I also want to change the sample rate to 44100Hz.
I'm trying to achieve this with AVAssetReader and AVAssetWriter, but not sure if its possible or if it's the best solution. Any other solution would be very much appreciated !
Here's my code so far :
// Input video audio (.mp4)
AVAsset *videoAsset = <mp4 video asset>;
NSArray<AVAssetTrack *> *videoAudioTracks = [videoAsset tracksWithMediaType:AVMediaTypeAudio];
AVAssetTrack *videoAudioTrack = [videoAudioTracks objectAtIndex:0];
// Output audio (.m4a AAC)
NSURL *exportUrl = <m4a, aac output file URL>;
// ASSET READER
NSError *error;
AVAssetReader *assetReader = [AVAssetReader assetReaderWithAsset:videoAsset
error:&error];
if(error) {
NSLog(#"error:%#",error);
return;
}
// Asset reader output
AVAssetReaderOutput *assetReaderOutput =[AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:videoAudioTrack
outputSettings:nil];
if(![assetReader canAddOutput:assetReaderOutput]) {
NSLog(#"Can't add output!");
return;
}
[assetReader addOutput:assetReaderOutput];
// ASSET WRITER
AVAssetWriter *assetWriter = [AVAssetWriter assetWriterWithURL:exportUrl
fileType:AVFileTypeAppleM4A
error:&error];
if(error) {
NSLog(#"error:%#",error);
return;
}
AudioChannelLayout channelLayout;
memset(&channelLayout, 0, sizeof(AudioChannelLayout));
channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
NSDictionary *outputSettings = #{AVFormatIDKey: #(kAudioFormatMPEG4AAC),
AVNumberOfChannelsKey: #2,
AVSampleRateKey: #44100.0F,
AVChannelLayoutKey: [NSData dataWithBytes:&channelLayout length:sizeof(AudioChannelLayout)],
AVEncoderBitRateKey: #64000};
/*NSDictionary *outputSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:kAudioFormatLinearPCM], AVFormatIDKey,
[NSNumber numberWithFloat:44100.f], AVSampleRateKey,
[NSNumber numberWithInt:2], AVNumberOfChannelsKey,
[NSData dataWithBytes:&channelLayout length:sizeof(AudioChannelLayout)], AVChannelLayoutKey,
[NSNumber numberWithInt:16], AVLinearPCMBitDepthKey,
[NSNumber numberWithBool:NO], AVLinearPCMIsNonInterleaved,
[NSNumber numberWithBool:NO],AVLinearPCMIsFloatKey,
[NSNumber numberWithBool:NO], AVLinearPCMIsBigEndianKey,
nil];*/
// Asset writer input
AVAssetWriterInput *assetWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio
outputSettings:outputSettings];
if ([assetWriter canAddInput:assetWriterInput])
[assetWriter addInput:assetWriterInput];
else {
NSLog(#"can't add asset writer input... die!");
return;
}
assetWriterInput.expectsMediaDataInRealTime = NO;
[assetWriter startWriting];
[assetReader startReading];
CMTime startTime = CMTimeMake (0, videoAudioTrack.naturalTimeScale);
[assetWriter startSessionAtSourceTime: startTime];
__block UInt64 convertedByteCount = 0;
dispatch_queue_t mediaInputQueue = dispatch_queue_create("mediaInputQueue", NULL);
[assetWriterInput requestMediaDataWhenReadyOnQueue:mediaInputQueue
usingBlock: ^
{
while (assetWriterInput.readyForMoreMediaData)
{
CMSampleBufferRef nextBuffer = [assetReaderOutput copyNextSampleBuffer];
if (nextBuffer)
{
// append buffer
[assetWriterInput appendSampleBuffer: nextBuffer];
convertedByteCount += CMSampleBufferGetTotalSampleSize (nextBuffer);
CMSampleBufferInvalidate(nextBuffer);
CFRelease(nextBuffer);
nextBuffer = NULL;
}
else
{
[assetWriterInput markAsFinished];
// [assetWriter finishWriting];
[assetReader cancelReading];
break;
}
}
}];
And here is the error I get with a video that contains an mp3 audio track :
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '*** -[AVAssetWriterInput
appendSampleBuffer:] Cannot append sample buffer: Input buffer must
be in an uncompressed format when outputSettings is not nil'
Any help would be much appreciated, thanks !
You should be able to achieve this by configuring your AVAssetReaderOutput output settings:
NSDictionary *readerOutputSettings = #{ AVSampleRateKey: #44100, AVFormatIDKey: #(kAudioFormatLinearPCM) };
AVAssetReaderOutput *assetReaderOutput =[AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:videoAudioTrack
outputSettings:readerOutputSettings];
I'm not native to Obj-C and I had to google around to figure out the accepted answer in Swift.
Here is the Swift version:
let audioSettings: [String : Any] = [
AVFormatIDKey: kAudioFormatLinearPCM,
AVSampleRateKey: 44100
]
let assetReaderAudioOutput = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: audioSettings)

Can't reverse AVAsset audio properly. The only result is white noise

I'm trying to reverse an AVAsset audio and save it to a file. To make things clear, I've made simple application with the issue https://github.com/ksenia-lyagusha/AudioReverse.git
The application takes mp4 video file from bundle, exports it to Temporary folder in the sandbox as single m4a file, then tries to read it from there, reverse and save result file back.
Temporary m4a file is OK.
The only result of my reverse part is Audio file in the Sandbox with white noise.
There is the part of code below, that is in charge of reversing AVAsset. It is based on related questions
How to reverse an audio file?
iOS audio manipulation - play local .caf file backwards
However, it doesn't work for me.
OSStatus theErr = noErr;
UInt64 fileDataSize = 0;
AudioFileID inputAudioFile;
AudioStreamBasicDescription theFileFormat;
UInt32 thePropertySize = sizeof(theFileFormat);
theErr = AudioFileOpenURL((__bridge CFURLRef)[NSURL URLWithString:inputPath], kAudioFileReadPermission, 0, &inputAudioFile);
thePropertySize = sizeof(fileDataSize);
theErr = AudioFileGetProperty(inputAudioFile, kAudioFilePropertyAudioDataByteCount, &thePropertySize, &fileDataSize);
UInt32 ps = sizeof(AudioStreamBasicDescription) ;
AudioFileGetProperty(inputAudioFile, kAudioFilePropertyDataFormat, &ps, &theFileFormat);
UInt64 dataSize = fileDataSize;
void *theData = malloc(dataSize);
// set up output file
AudioFileID outputAudioFile;
AudioStreamBasicDescription myPCMFormat;
myPCMFormat.mSampleRate = 44100;
myPCMFormat.mFormatID = kAudioFormatLinearPCM;
// kAudioFormatFlagsCanonical is deprecated
myPCMFormat.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsNonInterleaved;
myPCMFormat.mChannelsPerFrame = 1;
myPCMFormat.mFramesPerPacket = 1;
myPCMFormat.mBitsPerChannel = 32;
myPCMFormat.mBytesPerPacket = (myPCMFormat.mBitsPerChannel / 8) * myPCMFormat.mChannelsPerFrame;
myPCMFormat.mBytesPerFrame = myPCMFormat.mBytesPerPacket;
NSString *exportPath = [NSTemporaryDirectory() stringByAppendingPathComponent:#"ReverseAudio.caf"];
NSURL *outputURL = [NSURL fileURLWithPath:exportPath];
theErr = AudioFileCreateWithURL((__bridge CFURLRef)outputURL,
kAudioFileCAFType,
&myPCMFormat,
kAudioFileFlags_EraseFile,
&outputAudioFile);
//Read data into buffer
//if readPoint = dataSize, then bytesToRead = 0 in while loop and
//it is endless
SInt64 readPoint = dataSize-1;
UInt64 writePoint = 0;
while(readPoint > 0)
{
UInt32 bytesToRead = 2;
AudioFileReadBytes(inputAudioFile, false, readPoint, &bytesToRead, theData);
// bytesToRead is now the amount of data actually read
UInt32 bytesToWrite = bytesToRead;
AudioFileWriteBytes(outputAudioFile, false, writePoint, &bytesToWrite, theData);
// bytesToWrite is now the amount of data actually written
writePoint += bytesToWrite;
readPoint -= bytesToRead;
}
free(theData);
AudioFileClose(inputAudioFile);
AudioFileClose(outputAudioFile);
If I change file type in AudioFileCreateWithURL from kAudioFileCAFType to another the result file is not created in the Sandbox at all.
Thanks for any help.
You get white noise because your in and out file formats are incompatible. You have different sample rates and channels and probably other differences. To make this work you need to have a common (PCM) format mediating between reads and writes. This is a reasonable job for the new(ish) AVAudio frameworks. We read from file to PCM, shuffle the buffers, then write from PCM to file. This approach is not optimised for large files, as all data is read into the buffers in one go, but is enough to get you started.
You can call this method from your getAudioFromVideo completion block. Error handling ignored for clarity.
- (void)readAudioFromURL:(NSURL*)inURL reverseToURL:(NSURL*)outURL {
//prepare the in and outfiles
AVAudioFile* inFile =
[[AVAudioFile alloc] initForReading:inURL error:nil];
AVAudioFormat* format = inFile.processingFormat;
AVAudioFrameCount frameCount =(UInt32)inFile.length;
NSDictionary* outSettings = #{
AVNumberOfChannelsKey:#(format.channelCount)
,AVSampleRateKey:#(format.sampleRate)};
AVAudioFile* outFile =
[[AVAudioFile alloc] initForWriting:outURL
settings:outSettings
error:nil];
//prepare the forward and reverse buffers
self.forwaredBuffer =
[[AVAudioPCMBuffer alloc] initWithPCMFormat:format
frameCapacity:frameCount];
self.reverseBuffer =
[[AVAudioPCMBuffer alloc] initWithPCMFormat:format
frameCapacity:frameCount];
//read file into forwardBuffer
[inFile readIntoBuffer:self.forwaredBuffer error:&error];
//set frameLength of reverseBuffer to forwardBuffer framelength
AVAudioFrameCount frameLength = self.forwaredBuffer.frameLength;
self.reverseBuffer.frameLength = frameLength;
//iterate over channels
//stride is 1 or 2 depending on interleave format
NSInteger stride = self.forwaredBuffer.stride;
for (AVAudioChannelCount channelIdx = 0;
channelIdx < self.forwaredBuffer.format.channelCount;
channelIdx++) {
float* forwaredChannelData =
self.forwaredBuffer.floatChannelData[channelIdx];
float* reverseChannelData =
self.reverseBuffer.floatChannelData[channelIdx];
int32_t reverseIdx = 0;
//iterate over samples, allocate to reverseBuffer in reverse order
for (AVAudioFrameCount frameIdx = frameLength;
frameIdx >0;
frameIdx--) {
float sample = forwaredChannelData[frameIdx*stride];
reverseChannelData[reverseIdx*stride] = sample;
reverseIdx++;
}
}
//write reverseBuffer to outFile
[outFile writeFromBuffer:self.reverseBuffer error:nil];
}
I wasn't able to find the problem in your code, however I suggest you reversing AVAsset using AVAssetWriter. Following code is based on iOS reverse audio through AVAssetWritet. I've added additional method there to make it work. Finally I've got reversed file.
static NSMutableArray *samples;
static OSStatus sampler(CMSampleBufferRef sampleBuffer, CMItemCount index, void *refcon)
{
[samples addObject:(__bridge id _Nonnull)(sampleBuffer)];
return noErr;
}
- (void)reversePlayAudio:(NSURL *)inputURL
{
AVAsset *asset = [AVAsset assetWithURL:inputURL];
AVAssetReader* reader = [[AVAssetReader alloc] initWithAsset:asset error:nil];
AVAssetTrack* audioTrack = [[asset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
NSMutableDictionary* audioReadSettings = [NSMutableDictionary dictionary];
[audioReadSettings setValue:[NSNumber numberWithInt:kAudioFormatLinearPCM]
forKey:AVFormatIDKey];
AVAssetReaderTrackOutput* readerOutput = [AVAssetReaderTrackOutput assetReaderTrackOutputWithTrack:audioTrack outputSettings:audioReadSettings];
[reader addOutput:readerOutput];
[reader startReading];
NSDictionary *outputSettings = #{AVFormatIDKey : #(kAudioFormatMPEG4AAC),
AVSampleRateKey : #(44100.0),
AVNumberOfChannelsKey : #(1),
AVEncoderBitRateKey : #(128000),
AVChannelLayoutKey : [NSData data]};
AVAssetWriterInput *writerInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio
outputSettings:outputSettings];
NSString *exportPath = [NSTemporaryDirectory() stringByAppendingPathComponent:#"reverseAudio.m4a"];
NSURL *exportURL = [NSURL fileURLWithPath:exportPath];
NSError *writerError = nil;
AVAssetWriter *writer = [[AVAssetWriter alloc] initWithURL:exportURL
fileType:AVFileTypeAppleM4A
error:&writerError];
[writerInput setExpectsMediaDataInRealTime:NO];
writer.shouldOptimizeForNetworkUse = NO;
[writer addInput:writerInput];
[writer startWriting];
[writer startSessionAtSourceTime:kCMTimeZero];
CMSampleBufferRef sample;// = [readerOutput copyNextSampleBuffer];
samples = [[NSMutableArray alloc] init];
while (sample != NULL) {
sample = [readerOutput copyNextSampleBuffer];
if (sample == NULL)
continue;
CMSampleBufferCallForEachSample(sample, &sampler, NULL);
CFRelease(sample);
}
NSArray* reversedSamples = [[samples reverseObjectEnumerator] allObjects];
for (id reversedSample in reversedSamples) {
if (writerInput.readyForMoreMediaData) {
[writerInput appendSampleBuffer:(__bridge CMSampleBufferRef)(reversedSample)];
}
else {
[NSThread sleepForTimeInterval:0.05];
}
}
[samples removeAllObjects];
[writerInput markAsFinished];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^{
[writer finishWritingWithCompletionHandler:^{
// writing is finished
// reversed audio file in TemporaryDirectory in the Sandbox
}];
});
}
Known issues of the code.
There might be some problems with the memory, if the audio is long.
The audio file's duration is longer than original's. (As a quick fix you might cut it down as usual AVAsset).

How do you set the duration on a new file created with audioFileWritePackets?

I have concatenated 2 audio files using AudioFileReadPacketData to read the existing files and writing the data to a third file using AudioFileWritePackets. the source files seem to contain information about their duration. I can access it using the code below passing it the file url
-(float)lengthForUrl:(NSURL *)url
{
AVURLAsset* audioAsset = [AVURLAsset URLAssetWithURL:url options:nil];
CMTime audioDuration = audioAsset.duration;
float audioDurationSeconds = CMTimeGetSeconds(audioDuration);
return audioDurationSeconds;
}
when I make the same call on my new ( combined file) i get a duration of 0.
how can a set the duration on my new file ?
or preserve it in the transfer process ?
I did not figure out how to set the duration on my combined file but did
figure out how to create it with the duration intact.
I switched from using AssetReader / Writer to using ExtFileReader / Writer
-- working code is below
- (BOOL)combineFilesFor:(NSURL*)url
{
dispatch_async(dispatch_get_main_queue(), ^{
activity.hidden = NO;
[activity startAnimating];
});
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *soundOneNew = [documentsDirectory stringByAppendingPathComponent:Kcombined];
NSLog(#"%#",masterUrl);
NSLog(#"%#",url);
// create the url for the combined file
CFURLRef out_url = (__bridge CFURLRef)[NSURL fileURLWithPath:soundOneNew];
NSLog(#"out file url : %#", out_url);
ExtAudioFileRef af_in;
ExtAudioFileRef af_out = NULL;
Float64 existing_audioDurationSeconds = [self lengthForUrl:masterUrl];
NSLog(#"duration of existing file : %f", existing_audioDurationSeconds);
Float64 new_audioDurationSeconds = [self lengthForUrl:url];
NSLog(#"duration of existing file : %f", new_audioDurationSeconds);
Float64 combined_audioDurationSeconds = existing_audioDurationSeconds + new_audioDurationSeconds;
NSLog(#"duration of combined : %f", combined_audioDurationSeconds);
// put the urls of the files to combine in to an array
NSArray *sourceURLs = [NSArray arrayWithObjects:(__bridge id)((__bridge CFURLRef)
AudioStreamBasicDescription inputFileFormat;
AudioStreamBasicDescription converterFormat;
UInt32 thePropertySize = sizeof(inputFileFormat);
AudioStreamBasicDescription outputFileFormat;
#define BUFFER_SIZE 4096
UInt8 *buffer = NULL;
for (id in_url in sourceURLs) {
NSLog(#"url in = %#",in_url);
OSStatus in_stat = ExtAudioFileOpenURL ( (__bridge CFURLRef)(in_url), &af_in);
NSLog(#"in file open status : %d", (int)in_stat);
bzero(&inputFileFormat, sizeof(inputFileFormat));
in_stat = ExtAudioFileGetProperty(af_in, kExtAudioFileProperty_FileDataFormat,
&thePropertySize, &inputFileFormat);
NSLog(#"in file get property status : %d", (int)in_stat);
memset(&converterFormat, 0, sizeof(converterFormat));
converterFormat.mFormatID = kAudioFormatLinearPCM;
converterFormat.mSampleRate = inputFileFormat.mSampleRate;
converterFormat.mChannelsPerFrame = 1;
converterFormat.mBytesPerPacket = 2;
converterFormat.mFramesPerPacket = 1;
converterFormat.mBytesPerFrame = 2;
converterFormat.mBitsPerChannel = 16;
converterFormat.mFormatFlags = kAudioFormatFlagsNativeEndian |kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger;
in_stat = ExtAudioFileSetProperty(af_in, kExtAudioFileProperty_ClientDataFormat,
sizeof(converterFormat), &converterFormat);
NSLog(#"in file set property status : %d", (int)in_stat);
if (af_out == NULL) {
memset(&outputFileFormat, 0, sizeof(outputFileFormat));
outputFileFormat.mFormatID = kAudioFormatMPEG4AAC;
outputFileFormat.mFormatFlags = kMPEG4Object_AAC_Main;
outputFileFormat.mSampleRate = inputFileFormat.mSampleRate;
outputFileFormat.mChannelsPerFrame = inputFileFormat.mChannelsPerFrame;
// create the new file to write the combined file 2
OSStatus out_stat = ExtAudioFileCreateWithURL(out_url, kAudioFileM4AType, &outputFileFormat,
NULL, kAudioFileFlags_EraseFile, &af_out);
NSLog(#"out file create status : %d", out_stat);
out_stat = ExtAudioFileSetProperty(af_out, kExtAudioFileProperty_ClientDataFormat,
sizeof(converterFormat), &converterFormat);
NSLog(#"out file set property status : %d", out_stat);
}
// Buffer to read from source file and write to dest file
buffer = malloc(BUFFER_SIZE);
assert(buffer);
AudioBufferList conversionBuffer;
conversionBuffer.mNumberBuffers = 1;
conversionBuffer.mBuffers[0].mNumberChannels = inputFileFormat.mChannelsPerFrame;
conversionBuffer.mBuffers[0].mData = buffer;
conversionBuffer.mBuffers[0].mDataByteSize = BUFFER_SIZE;
while (TRUE) {
conversionBuffer.mBuffers[0].mDataByteSize = BUFFER_SIZE;
UInt32 frameCount = INT_MAX;
if (inputFileFormat.mBytesPerFrame > 0) {
frameCount = (conversionBuffer.mBuffers[0].mDataByteSize / inputFileFormat.mBytesPerFrame);
}
// Read a chunk of input
OSStatus err = ExtAudioFileRead(af_in, &frameCount, &conversionBuffer);
if (err) {
NSLog(#"error read from input file : %d", (int)err);
}else{
NSLog(#"read %d frames from inout file", frameCount);
}
// If no frames were returned, conversion is finished
if (frameCount == 0)
break;
// Write pcm data to output file
err = ExtAudioFileWrite(af_out, frameCount, &conversionBuffer);
if (err) {
NSLog(#"error write out file : %d", (int)err);
}else{
NSLog(#"wrote %d frames to out file", frameCount);
}
}
ExtAudioFileDispose(af_in);
}
ExtAudioFileDispose (af_out);
NSLog(#"combined file duration =: %f", [self lengthForUrl:[NSURL fileURLWithPath:soundOneNew]]);
// move the new file into place
[self deleteFileWithName:KUpdate forType:#""];
[self deleteFileWithName:KMaster forType:#""];
player = nil;
[self replaceFileAtPath:KMaster withData:[NSData dataWithContentsOfFile:soundOneNew]];
[self deleteFileWithName:Kcombined forType:#""];
[self pauseRecording];
dispatch_async(dispatch_get_main_queue(), ^{
activity.hidden = YES;
[activity stopAnimating];
record.enabled = YES;
});
return YES;
}

AVAssetWriter memory error

I have few methods that are supposed to write video in mov file to temp dir, but after ~15 sec. I'm getting errors:
Received memory warning.
Received memory warning.
Received memory warning.
Received memory warning.
Then app is crashing. I'm stuck and have no idea what is wrong...
- (void) saveVideoToFileFromBuffer:(CMSampleBufferRef) buffer {
if (!movieWriter) {
NSString *moviePath = [NSString stringWithFormat:#"%#tmpMovie", NSTemporaryDirectory()];
if ([[NSFileManager defaultManager] fileExistsAtPath:moviePath])
[self removeMovieAtPath:moviePath];
NSError *error = nil;
movieWriter = [[AVAssetWriter alloc] initWithURL:[NSURL fileURLWithPath:moviePath] fileType: AVFileTypeQuickTimeMovie error:&error];
if (error) {
m_log(#"Error allocating AssetWriter: %#", [error localizedDescription]);
} else {
CMFormatDescriptionRef description = CMSampleBufferGetFormatDescription(buffer);
if(![self setUpMovieWriterObjectWithDescriptor:description])
m_log(#"ET go home, no video recording!!");
}
}
if (movieWriter.status != AVAssetWriterStatusWriting) {
[movieWriter startWriting];
[movieWriter startSessionAtSourceTime:kCMTimeZero];
apiStatusChangeIndicator = NO;
}
if (movieWriter.status == AVAssetWriterStatusWriting) {
if (![movieInput appendSampleBuffer:buffer]) m_log(#"Failed to append sample buffer!");
}
}
Rest of code:
- (BOOL) setUpMovieWriterObjectWithDescriptor:(CMFormatDescriptionRef) descriptor {
CMVideoDimensions dimensions = CMVideoFormatDescriptionGetDimensions(descriptor);
NSDictionary *compressionSettings = [NSDictionary dictionaryWithObjectsAndKeys: AVVideoProfileLevelH264Baseline31,AVVideoProfileLevelKey,
[NSNumber numberWithInteger:30], AVVideoMaxKeyFrameIntervalKey, nil];
//AVVideoProfileLevelKey set because of errors
NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:AVVideoCodecH264, AVVideoCodecKey,[NSNumber numberWithInt:dimensions.width], AVVideoWidthKey,
[NSNumber numberWithInt:dimensions.height], AVVideoHeightKey, compressionSettings, AVVideoCompressionPropertiesKey, nil];
if ([movieWriter canApplyOutputSettings:videoSettings forMediaType:AVMediaTypeVideo]) {
movieInput = [[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
movieInput.expectsMediaDataInRealTime = YES;
if ([movieWriter canAddInput:movieInput]) {
[movieWriter addInput:movieInput];
} else {
m_log(#"Couldn't apply video input to Asset Writer!");
return NO;
}
} else {
m_log(#"Couldn't apply video settings to AVAssetWriter!");
return NO;
}
return YES;
}
Would be great if someone could point my mistake! Can share more code if needed. SampleBuffer comes from CIImage with filters.
Now new thing, I can record few seconds of movie and saved it, but it's all black screen...
UPDATE
Saving video works, but creating CMSampleBufferRef from CIImage fails. It's reason that I got green or black screen, here's the code:
- (CMSampleBufferRef) processCIImageToPixelBuffer:(CIImage*) image andSampleBuffer:(CMSampleTimingInfo) info{
CVPixelBufferRef renderTargetPixelBuffer;
CFDictionaryRef empty;
CFMutableDictionaryRef attrs;
empty = CFDictionaryCreate(kCFAllocatorDefault,
NULL,
NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
attrs = CFDictionaryCreateMutable(kCFAllocatorDefault,
1,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(attrs,
kCVPixelBufferIOSurfacePropertiesKey,
empty);
CVReturn cvError = CVPixelBufferCreate(kCFAllocatorSystemDefault,
[image extent].size.width,
[image extent].size.height,
kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
attrs,
&renderTargetPixelBuffer);
if (cvError != 0) {
m_log(#"Error when init Pixel buffer: %i", cvError);
}
CFRelease(empty);
CFRelease(attrs);
CVPixelBufferLockBaseAddress(renderTargetPixelBuffer, 0 );
[_coreImageContext render:image toCVPixelBuffer:renderTargetPixelBuffer];
CVPixelBufferUnlockBaseAddress(renderTargetPixelBuffer, 0 );
CMVideoFormatDescriptionRef videoInfo = NULL;
CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, renderTargetPixelBuffer, &videoInfo);
CMSampleBufferRef recordingBuffer;
OSStatus cmError = CMSampleBufferCreateForImageBuffer(kCFAllocatorDefault, renderTargetPixelBuffer, true, NULL, NULL, videoInfo, &info, &recordingBuffer);
if (cmError != 0 ) {
m_log(#"Error creating sample buffer: %i", (int)cmError);
}
CVPixelBufferRelease(renderTargetPixelBuffer);
renderTargetPixelBuffer = NULL;
CFRelease(videoInfo);
videoInfo = NULL;
return recordingBuffer;
}
You should check your code with Profile tool. Especially for memory leaks. May be you do not release sample buffer:
CMSampleBufferInvalidate(buffer);
CFRelease(buffer);
buffer = NULL;

AVAssetWriter can't create m4a file on iPhone 3G

I'm trying to use AVAssetWriter to create an m4a file. It works perfectly on any devices exclude iPhone 3G. Some says, that problem is in 3G does not support AAC encoding. Is it true? if ([assetWriter canAddInput:assetWriterInput]) returns NO.
AVAssetWriter *assetWriter = [AVAssetWriter assetWriterWithURL:exportURL
fileType:AVFileTypeAppleM4A
error:&assetError];
if (assetError) {
NSLog (#"error: %#", assetError);
return;
}
AudioChannelLayout channelLayout;
memset(&channelLayout, 0, sizeof(AudioChannelLayout));
channelLayout.mChannelBitmap = 0;
channelLayout.mNumberChannelDescriptions = 0;
channelLayout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo;
NSDictionary *outputSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[ NSNumber numberWithInt: kAudioFormatMPEG4AAC], AVFormatIDKey,
[ NSNumber numberWithInt: 2], AVNumberOfChannelsKey,
[ NSNumber numberWithFloat: 44100.0], AVSampleRateKey,
[ NSData dataWithBytes: &channelLayout length:sizeof( AudioChannelLayout ) ], AVChannelLayoutKey,
[ NSNumber numberWithInt: 192000], AVEncoderBitRateKey,nil];
AVAssetWriterInput *assetWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio
outputSettings:outputSettings];
if ([assetWriter canAddInput:assetWriterInput]) {
[assetWriter addInput:assetWriterInput];
}
Anybody knows, how to create m4a file on a iPhone 3G?
Thanks
As you're saying, iPhone 3G does not support "built in" encoding of AAC (sorry, no direct link, scroll about a page down to table 1-2) so there is no way to do it without bundling your own encoder.
The problem is that bundling an AAC encoder requires a patent license. If that's something you can deal with, you should be able to compile faad2 for the iPhone and use that.

Resources