Reputation: 1185
The question is how do you find and move the moov atom of a .mov file that is recorded on an iOS device so you can stream it over http. There is one way to do it but that requires to export it to file which in theory makes you copy the entire file over and then you would be able to stream it.
Is there any other way to do this?
Upvotes: 2
Views: 3229
Reputation: 287
Using iOS AV Foundation framework and a few lines of Objective-C (you can also convert from MOV to MP4 since Android cannot read MOV):
so using this code without buffer play smooth video from Live URL its works but before upload video to your server use this code and convert your video and after upload it. so video is play video like snapchat without any load.
Don't forgot to add this below framework into your Project.😊
#import <AVFoundation/AVAsset.h> #import <AVFoundation/AVAssetExportSession.h> #import <AVFoundation/AVMediaFormat.h>
+ (void) convertVideoToMP4AndFixMooV: (NSString*)filename toPath:(NSString*)outputPath {
NSURL *url = [NSURL fileURLWithPath:filename];
AVAsset *avAsset = [AVURLAsset URLAssetWithURL:url options:nil];
AVAssetExportSession *exportSession = [AVAssetExportSession
exportSessionWithAsset:avAsset
presetName:AVAssetExportPresetPassthrough];
exportSession.outputURL = [NSURL fileURLWithPath:outputPath];
exportSession.outputFileType = AVFileTypeAppleM4V;
// This should move the moov atom before the mdat atom,
// hence allow playback before the entire file is downloaded
exportSession.shouldOptimizeForNetworkUse = YES;
[exportSession exportAsynchronouslyWithCompletionHandler:
^{
if (AVAssetExportSessionStatusCompleted == exportSession.status) {}
else if (AVAssetExportSessionStatusFailed == exportSession.status) {
NSLog(@"AVAssetExportSessionStatusFailed");
}
else
{
NSLog(@"Export Session Status: %d", exportSession.status);
}
}];
}
Upvotes: 5
Reputation: 1185
Here is the code to a method I edited and wrote to work with iOS to find and then write the moov atom to specific location so then right after you have these two parts of the video file you just need to access the bytes of the video file from where the FTYP atom ends(take a look at the variables marked as start_offset and last_offset) and the streaming will occur.
- (NSString *)fixForFastPlayback:(char*)dest:(ALAsset*)selected
{
FILE *infile = NULL;
FILE *outfile = NULL;
uint32_t atom_type = 0;
uint64_t atom_size = 0;
uint64_t atom_offset = 0;
uint64_t last_offset;
uint64_t moov_atom_size;
uint64_t ftyp_atom_size = 0;
uint64_t i, j;
uint32_t offset_count;
uint64_t current_offset;
uint64_t start_offset = 0;
ALAssetRepresentation * rep = [[selected defaultRepresentation] retain];
int bufferSize = 8192; // or use 8192 size as read from other posts
int read = 0;
NSError * err = nil;
uint8_t * buffer = calloc(bufferSize, sizeof(*buffer));
uint8_t * ftyp_atom;
/* traverse through the atoms in the file to make sure that 'moov' is
* at the end */
int asset_offset = 0;
while (asset_offset < [rep size])
{
read = [rep getBytes:buffer
fromOffset:asset_offset
length:ATOM_PREAMBLE_SIZE
error:&err];
asset_offset += read;
if (err != nil)
{
NSLog(@"Error: %@ %@", err, [err userInfo]);
}
atom_size = (uint32_t)BE_32(&buffer[0]);
atom_type = BE_32(&buffer[4]);
/* keep ftyp atom */
if (atom_type == FTYP_ATOM) //no idea what an atom is, maybe a header or some sort of meta data or a file marker
{
ftyp_atom_size = atom_size;
ftyp_atom = calloc(ftyp_atom_size, sizeof(*buffer));
if (!ftyp_atom)
{
printf ("could not allocate %"PRIu64" byte for ftyp atom\n",
atom_size);
}
asset_offset -= ATOM_PREAMBLE_SIZE;
read = [rep getBytes:ftyp_atom
fromOffset:asset_offset
length:ftyp_atom_size
error:&err];
asset_offset += read;
start_offset = asset_offset;
}
else
{
asset_offset += (atom_size - ATOM_PREAMBLE_SIZE);
}
printf("%c%c%c%c %10"PRIu64" %"PRIu64"\n",
(atom_type >> 24) & 255,
(atom_type >> 16) & 255,
(atom_type >> 8) & 255,
(atom_type >> 0) & 255,
atom_offset,
atom_size);
if ((atom_type != FREE_ATOM) &&
(atom_type != JUNK_ATOM) &&
(atom_type != MDAT_ATOM) &&
(atom_type != MOOV_ATOM) &&
(atom_type != PNOT_ATOM) &&
(atom_type != SKIP_ATOM) &&
(atom_type != WIDE_ATOM) &&
(atom_type != PICT_ATOM) &&
(atom_type != UUID_ATOM) &&
(atom_type != FTYP_ATOM))
{
printf ("encountered non-QT top-level atom (is this a Quicktime file?)\n");
break;
}
atom_offset += atom_size;
/* The atom header is 8 (or 16 bytes), if the atom size (which
* includes these 8 or 16 bytes) is less than that, we won't be
* able to continue scanning sensibly after this atom, so break. */
if (atom_size < 8)
break;
}
if (atom_type != MOOV_ATOM)
{
printf ("last atom in file was not a moov atom\n");
free(ftyp_atom);
fclose(infile);
return 0;
}
asset_offset = [rep size];
asset_offset -= atom_size;
last_offset = asset_offset;
moov_atom_size = atom_size;
uint8_t * moov_atom = calloc(moov_atom_size, sizeof(*buffer));
if (!moov_atom)
{
printf ("could not allocate %"PRIu64" byte for moov atom\n",
atom_size);
}
read = [rep getBytes:moov_atom
fromOffset:asset_offset
length:moov_atom_size
error:&err];
asset_offset += read;
/* this utility does not support compressed atoms yet, so disqualify
* files with compressed QT atoms */
if (BE_32(&moov_atom[12]) == CMOV_ATOM)
{
printf ("this utility does not support compressed moov atoms yet\n");
}
/* crawl through the moov chunk in search of stco or co64 atoms */
for (i = 4; i < moov_atom_size - 4; i++)
{
atom_type = BE_32(&moov_atom[i]);
if (atom_type == STCO_ATOM)
{
printf (" patching stco atom...\n");
atom_size = BE_32(&moov_atom[i - 4]);
if (i + atom_size - 4 > moov_atom_size)
{
printf (" bad atom size\n");
}
offset_count = BE_32(&moov_atom[i + 8]);
for (j = 0; j < offset_count; j++)
{
current_offset = BE_32(&moov_atom[i + 12 + j * 4]);
current_offset += moov_atom_size;
moov_atom[i + 12 + j * 4 + 0] = (current_offset >> 24) & 0xFF;
moov_atom[i + 12 + j * 4 + 1] = (current_offset >> 16) & 0xFF;
moov_atom[i + 12 + j * 4 + 2] = (current_offset >> 8) & 0xFF;
moov_atom[i + 12 + j * 4 + 3] = (current_offset >> 0) & 0xFF;
}
i += atom_size - 4;
}
else if (atom_type == CO64_ATOM)
{
printf (" patching co64 atom...\n");
atom_size = BE_32(&moov_atom[i - 4]);
if (i + atom_size - 4 > moov_atom_size)
{
printf (" bad atom size\n");
}
offset_count = BE_32(&moov_atom[i + 8]);
for (j = 0; j < offset_count; j++)
{
current_offset = BE_64(&moov_atom[i + 12 + j * 8]);
current_offset += moov_atom_size;
moov_atom[i + 12 + j * 8 + 0] = (current_offset >> 56) & 0xFF;
moov_atom[i + 12 + j * 8 + 1] = (current_offset >> 48) & 0xFF;
moov_atom[i + 12 + j * 8 + 2] = (current_offset >> 40) & 0xFF;
moov_atom[i + 12 + j * 8 + 3] = (current_offset >> 32) & 0xFF;
moov_atom[i + 12 + j * 8 + 4] = (current_offset >> 24) & 0xFF;
moov_atom[i + 12 + j * 8 + 5] = (current_offset >> 16) & 0xFF;
moov_atom[i + 12 + j * 8 + 6] = (current_offset >> 8) & 0xFF;
moov_atom[i + 12 + j * 8 + 7] = (current_offset >> 0) & 0xFF;
}
i += atom_size - 4;
}
}
outfile = fopen(dest, "wb");
NSLog(@"%llu",last_offset);
//global variables to be used when returning the actual data
start_offset_not_c = start_offset;
last_offset_not_c = last_offset;
if (ftyp_atom_size > 0)
{
printf ("writing ftyp atom...\n");
if (fwrite(ftyp_atom, ftyp_atom_size, 1, outfile) != 1)
{
perror(dest);
}
}
printf ("writing moov atom...\n");
if (fwrite(moov_atom, moov_atom_size, 1, outfile) != 1)
{
perror(dest);
}
fclose(outfile);
free(ftyp_atom);
free(moov_atom);
ftyp_atom = NULL;
moov_atom = NULL;
return [NSString stringWithCString:dest encoding:NSStringEncodingConversionAllowLossy];
}
Enjoy!
Upvotes: 3