关于分辨率与帧的介绍

转载一遍关于分辨率与帧介绍的文章,感觉比较详细,来自于:http://nvtrlab.jp/blog/andy/iphoneからmp4ファイルの解像度やビットレートなどを変.html#wrap

【iOS】iPhoneからMP4ファイルの解像度やビットレートなどを変更して書き出す方法

今回は、予め用意したMP4のファイルをiPhoneで読み込み、解像度やビットレートなどの変更を加えて別のMP4ファイルに出力したいという事がありましたので、その方法を書きたいと思います。


一般的にファイルの出力を行う方法として、


exportAsynchronouslyWithCompletionHandler:


メソッドを使用する方法が説明されていますが、この方法だと予め用意されたプリセットのみでしか出力ファイルを作成できないため、例えばSNSなどで要求されているフォーマットに対応できなくなります。という事で、詳細な設定を行うために、


requestMediaDataWhenReadyOnQueue:usingBlock:


メソッドを使用します。


今回は、はじめにMP4ファイルを用意いただき、Resoursesフォルダに追加しておいてください。

コード

それではコードから。
まず、ヘッダーファイルです。

1
2
3
4
5
6
7
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import <AssetsLibrary/AssetsLibrary.h>
 
@interface ExportMovieViewController : UIViewController
 
@end



次に実装コードです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#import "ExportMovieViewController.h"
 
@interface ExportMovieViewController ()
{
    AVAssetWriter* videoWriter;
    int writeFrames;
    NSMutableDictionary* presets;
}
 
@end
 
@implementation ExportMovieViewController
 
- (void)viewDidLoad
{
    [super viewDidLoad];
     
//解説-1
    presets = [NSMutableDictionary dictionary];
    [presets setObject:[NSNumber numberWithFloat:480.0f] forKey:@"width"];
    [presets setObject:[NSNumber numberWithFloat:480.0f] forKey:@"height"];
    [presets setObject:@"MP4" forKey:@"video_format"];
    [presets setObject:[NSNumber numberWithFloat:1500000.0f] forKey:@"video_bitrate"];
    [presets setObject:[NSNumber numberWithFloat:30.00f] forKey:@"framerate"];
     
    AVMutableComposition* comp = [self makeComposition];
    if (comp) {
        [self exportWithComposition:comp];
    }
     
}
 
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}
 
//解説-2
- (AVMutableComposition*)makeComposition
{
    AVMutableComposition* comp = [AVMutableComposition composition];
    AVMutableCompositionTrack* compVideoTrack = [comp addMutableTrackWithMediaType:AVMediaTypeVideo
                                                                  preferredTrackID:kCMPersistentTrackID_Invalid];
 
    NSBundle* bundle = [NSBundle mainBundle];
    NSString* path = [bundle pathForResource:@"[Resourcesフォルダ内のファイル名]" ofType:@"mp4"];
    AVURLAsset* asset = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:path]];
 
    AVAssetTrack* videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
    [compVideoTrack insertTimeRange:videoTrack.timeRange
                            ofTrack:videoTrack
                             atTime:kCMTimeZero error:nil];
     
    return comp;
}
 
//解説-3
- (NSDictionary*)getVideoCompressionSettings
{
    NSDictionary *videoCleanApertureSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                                [NSNumber numberWithFloat:[[presets objectForKey:@"width"] floatValue]], AVVideoCleanApertureWidthKey,
                                                [NSNumber numberWithFloat:[[presets objectForKey:@"height"] floatValue]], AVVideoCleanApertureHeightKey,
                                                [NSNumber numberWithInt:10], AVVideoCleanApertureHorizontalOffsetKey,
                                                [NSNumber numberWithInt:10], AVVideoCleanApertureVerticalOffsetKey,
                                                nil];
     
     
    NSDictionary *codecSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                   [NSNumber numberWithFloat:[[presets objectForKey:@"video_bitrate"] floatValue]], AVVideoAverageBitRateKey,
                                   [NSNumber numberWithFloat:[[presets objectForKey:@"framerate"] floatValue]],AVVideoMaxKeyFrameIntervalKey,
                                   videoCleanApertureSettings, AVVideoCleanApertureKey,
                                   nil];
     
     
     
    NSDictionary *videoCompressionSettings = [NSDictionary dictionaryWithObjectsAndKeys:
                                              AVVideoCodecH264, AVVideoCodecKey,
                                              codecSettings,AVVideoCompressionPropertiesKey,
                                              [NSNumber numberWithFloat:[[presets objectForKey:@"width"] floatValue]], AVVideoWidthKey,
                                              [NSNumber numberWithFloat:[[presets objectForKey:@"height"] floatValue]], AVVideoHeightKey,
                                              nil];
    return videoCompressionSettings;
}
 
- (void)exportWithComposition:(AVMutableComposition*)comp
{
    NSError *error = nil;
    NSString *exportPath = [NSHomeDirectory() stringByAppendingPathComponent:@"tmp/temp.mov"];
    NSURL *exportUrl = [NSURL fileURLWithPath:exportPath];
     
    if ([[NSFileManager defaultManager] fileExistsAtPath:exportPath])
    {
        [[NSFileManager defaultManager] removeItemAtPath:exportPath error:nil];
    }
     
//解説-4
    videoWriter = [[AVAssetWriter alloc] initWithURL:exportUrl fileType:AVFileTypeQuickTimeMovie error:&error];
     
    AVAssetWriterInput* videoWriterInput = [AVAssetWriterInput
                                            assetWriterInputWithMediaType:AVMediaTypeVideo
                                            outputSettings:[self getVideoCompressionSettings]];
     
    videoWriterInput.expectsMediaDataInRealTime = YES;
    [videoWriter addInput:videoWriterInput];
 
//解説-5
    NSError *aerror = nil;
    AVAssetReader *reader = [[AVAssetReader alloc] initWithAsset:comp error:&aerror];
    AVAssetTrack *videoTrack = [[comp tracksWithMediaType:AVMediaTypeVideo]objectAtIndex:0];
     
    videoWriterInput.transform = videoTrack.preferredTransform;
    NSDictionary *videoOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] forKey:(id)kCVPixelBufferPixelFormatTypeKey];
 
    AVAssetReaderTrackOutput *readerVideoOutput = [[AVAssetReaderTrackOutput alloc] initWithTrack:videoTrack outputSettings:videoOptions];
     
    [reader addOutput:readerVideoOutput];
 
    [videoWriter startWriting];
    [videoWriter startSessionAtSourceTime:kCMTimeZero];
    [reader startReading];
     
    writeFrames = 0;
 
    dispatch_queue_t _processingQueue = dispatch_queue_create("assetVideoWriterQueue", NULL);
     
    __weak AVAssetWriterInput* weakWriterInput = videoWriterInput;
    __weak AVAssetWriter* weakWriter = videoWriter;
    [videoWriterInput requestMediaDataWhenReadyOnQueue:_processingQueue usingBlock:
     ^{
         bool isError = NO;
 
//解説-6
         while ([weakWriterInput isReadyForMoreMediaData]){
             CMSampleBufferRef videoSampleBuffer = [readerVideoOutput copyNextSampleBuffer];
             if (videoSampleBuffer) {
                 CMSampleBufferRef newSampleBuffer = [self offsetTimmingWithSampleBufferForVideo:videoSampleBuffer];
                 BOOL result = [weakWriterInput appendSampleBuffer:newSampleBuffer];
                  
                 if (!result) {
                     [reader cancelReading];
                     NSLog(@"NO RESULT");
                     NSLog(@"videoWriter.error: %@", weakWriter.error);
                     isError = YES;
                     break;
                 }
                 CFRelease(videoSampleBuffer);
                 CFRelease(newSampleBuffer);
                 writeFrames++;
             } else {
                 [weakWriterInput markAsFinished];
                 break;
             }
         }
  
//解説-7
         while (1) {
             if ([reader status] == AVAssetReaderStatusCompleted) {
                 if (!isError) {
                     dispatch_async(dispatch_get_main_queue(), ^(){
                         NSLog(@"AVAssetReaderStatusCompleted");
                         [self videoWriterFinish];
                     });
                 }
                 break;
             }
         }
     }];
}
 
- (void)videoWriterFinish
{
    [videoWriter finishWritingWithCompletionHandler:^(){
        NSString *exportPath = [NSHomeDirectory() stringByAppendingPathComponent:@"tmp/temp.mov"];
        NSURL *exportUrl = [NSURL fileURLWithPath:exportPath];
         
        ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init];
        [library writeVideoAtPathToSavedPhotosAlbum:exportUrl
                                                completionBlock:^(NSURL *assetURL, NSError *assetError) {
                                                     
                                                    if (assetError) {
                                                        NSLog(@"export error!!!!");
                                                    } else {
                                                        NSLog(@"export finished!!");
                                                    }
                                                     
                                                    NSFileManager *manager = [NSFileManager defaultManager];
                                                    if ([manager fileExistsAtPath:assetURL.absoluteString isDirectory:NO]) {
                                                        [manager removeItemAtPath:assetURL.absoluteString error:nil];
                                                    }
                                                     
                                                     
                                                }];
    }];
}
 
- (CMSampleBufferRef)offsetTimmingWithSampleBufferForVideo:(CMSampleBufferRef)sampleBuffer
{
    CMSampleBufferRef newSampleBuffer;
    float framerate = [[presets objectForKey:@"framerate"] floatValue];
    CMSampleTimingInfo sampleTimingInfo;
    sampleTimingInfo.duration = CMTimeMake(100, framerate * 100);
    sampleTimingInfo.presentationTimeStamp = CMTimeMake((writeFrames + 0) * 100, framerate * 100);
    sampleTimingInfo.decodeTimeStamp = kCMTimeInvalid;
     
    CMSampleBufferCreateCopyWithNewTiming(kCFAllocatorDefault,
                                          sampleBuffer,
                                          1,
                                          &sampleTimingInfo,
                                          &newSampleBuffer);
     
    return newSampleBuffer;
}
 
@end


コード解説

それでは解説です。

解説−1

この部分が最終的に出力されるMP4ファイルの設定値です。
解像度の部分は、オリジナルファイルの縦横比と比率が異なる場合には、画像が変形されますの注意が必要です。予めAVMutableCompositionを作成する段階でクロップなどの処理を行ってから出力する必要があります。
ビットレートの部分は、設定されたビットレートよりも少し小さくなるようです。これはアベレージ値のためかもしれません。
フレームレートの部分は、オリジナルファイルのフレームレートと異なる場合に再生されるムービーの長さが変化します。これは、1フレームあたりの再生時間を変更しているためです。

解説-2

リソースからAVMutableCompositionを作成しています。
予め複数の動画をつなげて出力したい場合などは、この部分にコードを追加していきます。

解説-3

AVAssetWriterでファイル出力する際のビデオの設定を作成しています。先ほど解説-1で設定した値を使用しています。

解説-4

AVAssetWriterを作成し、AVAssetWriterInputを追加しています。AVAssetWriterInputでは、先ほど解説-3で説明した設定を使用しています。

解説-5

この部分で、ファイルからデータを読み出すためのAVAssetReaderを作成しています。解説-2で作成したAVMutableCompositionからビデオトラックを取り出し、AVAssetReaderTrackOutputを作成しています。

解説-6

この部分でビデオをバッファリングしています。ここで前回のブログで紹介したCMSampleBufferRefのタイムスタンプ差し替えを行っています。始めはこの処理を行わずに出力していたのですが、その方法だとフレームレートが設定した値になりませんでした。タイムスタンプ上のtimeScale値を表示させてみると、最初のフレームで1、最終のフレームで0という値が返ってきていたためにそれが原因のようです。通常この値はフレームレートの値になります。

解説-7

AVAssetReaderの作業が終了した時点でPhotoAlbumに作成したファイルを追加しています。


という感じになります。この方法ではフレームレートを変更した場合にオリジナルのムービーの長さを維持できません。これを維持しようとすると、かなり高度な方法で映像を補完しなければなりませんので、また考えが浮かんだら作ってみたいと思います。
簡易的なスローを作成する場合には、使用できるのではないかと思います。


今回はここまで。
次回はまだ内容を考えていませんが、アプリを作っている最中に見つけた事を書きたいと思います


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值