Is there any way to record onto the end of an audio file? We can't just pause the recording instead of stopping it, because the user needs to be able to come back to the app later and add more audio to their recording. Currently, the audio is stored in CoreData as NSData. NSData's AppendData
does not work because the resulting audio file still reports that it is only as long as the original data.
Another possibility would be taking the original audio file, along with the new one, and concatenate them into one audio file, if there's any way to do that.
We had the same requirements for our app as the OP described, and ran into the same issues (i.e., the recording has to be stopped, instead of paused, if the user wants to listen to what she has recorded up to that point). Our app (project's Github repo) uses AVQueuePlayer
for playback and a method similar to kermitology's answer to concatenate the partial recordings, with some notable differences:
The rationale behind the last item is that simple recordings with AVAudioRecorder
will have one track, and the main reason for this whole workaround is to concatenate those single tracks in the assets (see Addendum 3). So why not use AVMutableComposition
's insertTimeRange
method instead, that takes an AVAsset
instead of an AVAssetTrack
?
Relevant parts: (full code)
import UIKit
import AVFoundation
class RecordViewController: UIViewController {
/* App allows volunteers to record newspaper articles for the
blind and print-impaired, hence the name.
*/
var articleChunks = [AVURLAsset]()
func concatChunks() {
let composition = AVMutableComposition()
/* `CMTimeRange` to store total duration and know when to
insert subsequent assets.
*/
var insertAt = CMTimeRange(start: kCMTimeZero, end: kCMTimeZero)
repeat {
let asset = self.articleChunks.removeFirst()
let assetTimeRange =
CMTimeRange(start: kCMTimeZero, end: asset.duration)
do {
try composition.insertTimeRange(assetTimeRange,
of: asset,
at: insertAt.end)
} catch {
NSLog("Unable to compose asset track.")
}
let nextDuration = insertAt.duration + assetTimeRange.duration
insertAt = CMTimeRange(start: kCMTimeZero, duration: nextDuration)
} while self.articleChunks.count != 0
let exportSession =
AVAssetExportSession(
asset: composition,
presetName: AVAssetExportPresetAppleM4A)
exportSession?.outputFileType = AVFileType.m4a
exportSession?.outputURL = /* create URL for output */
// exportSession?.metadata = ...
exportSession?.exportAsynchronously {
switch exportSession?.status {
case .unknown?: break
case .waiting?: break
case .exporting?: break
case .completed?: break
case .failed?: break
case .cancelled?: break
case .none: break
}
}
/* Clean up (delete partial recordings, etc.) */
}
This diagram helped me to get around what expects what and inherited from where. (NSObject
is implicitly implied as superclass where there is no inheritance arrow.)
Addendum 1: I had my reservations regarding the switch
part instead of using KVO on AVAssetExportSessionStatus
, but the docs are clear that exportAsynchronously
's callback block "is invoked when writing is complete or in the event of writing failure".
Addendum 2: Just in case if someone has issues with AVQueuePlayer
: 'An AVPlayerItem cannot be associated with more than one instance of AVPlayer'
Addendum 3: Unless you are recording in stereo, but mobile devices have one input as far as I know. Also, using fancy audio mixing would also require the use of AVCompositionTrack
. A good SO thread: Proper AVAudioRecorder Settings for Recording Voice?
This can be done fairly easily using AVMutableComposionTrack insertTimeRange:ofTrack:atTime:error
. The code is somewhat lengthy, but it's really just like 4 steps:
// Create a new audio track we can append to
AVMutableComposition* composition = [AVMutableComposition composition];
AVMutableCompositionTrack* appendedAudioTrack =
[composition addMutableTrackWithMediaType:AVMediaTypeAudio
preferredTrackID:kCMPersistentTrackID_Invalid];
// Grab the two audio tracks that need to be appended
AVURLAsset* originalAsset = [[AVURLAsset alloc]
initWithURL:[NSURL fileURLWithPath:originalAudioPath] options:nil];
AVURLAsset* newAsset = [[AVURLAsset alloc]
initWithURL:[NSURL fileURLWithPath:newAudioPath] options:nil];
NSError* error = nil;
// Grab the first audio track and insert it into our appendedAudioTrack
AVAssetTrack *originalTrack = [originalAsset tracksWithMediaType:AVMediaTypeAudio];
CMTimeRange timeRange = CMTimeRangeMake(kCMTimeZero, originalAsset.duration);
[appendedAudioTrack insertTimeRange:timeRange
ofTrack:[originalTrack objectAtIndex:0]
atTime:kCMTimeZero
error:&error];
if (error)
{
// do something
return;
}
// Grab the second audio track and insert it at the end of the first one
AVAssetTrack *newTrack = [newAsset tracksWithMediaType:AVMediaTypeAudio];
timeRange = CMTimeRangeMake(kCMTimeZero, newAsset.duration);
[appendedAudioTrack insertTimeRange:timeRange
ofTrack:[newTrack objectAtIndex:0]
atTime:originalAsset.duration
error:&error];
if (error)
{
// do something
return;
}
// Create a new audio file using the appendedAudioTrack
AVAssetExportSession* exportSession = [AVAssetExportSession
exportSessionWithAsset:composition
presetName:AVAssetExportPresetAppleM4A];
if (!exportSession)
{
// do something
return;
}
NSString* appendedAudioPath= @""; // make sure to fill this value in
exportSession.outputURL = [NSURL fileURLWithPath:appendedAudioPath];
exportSession.outputFileType = AVFileTypeAppleM4A;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
// exported successfully?
switch (exportSession.status)
{
case AVAssetExportSessionStatusFailed:
break;
case AVAssetExportSessionStatusCompleted:
// you should now have the appended audio file
break;
case AVAssetExportSessionStatusWaiting:
break;
default:
break;
}
NSError* error = nil;
}];
You can append two audio files by creating a AVMutableCompositionTrack
after adding the two files and exporting the composition using exportAsynchronouslyWithCompletionHandler
method of AVAssetExportSession
.
Please refer below links for more details.
AVAssetExportSession Class Reference
Creating New Assets
Hope this helps to solve your issue.
I don't have a complete code example but the Extended Audio File Services can help you concatenate two audio files. Search for Extended Audio File Services in Xcode or visit the link below.
Apple documentation
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With