自定義相機採集及視訊編輯(1)-短視訊錄製
自定義相機採集及視訊編輯
iOS呼叫系統的相簿(顯示中文的標題)
在info.plist加上這一條這樣就可以使我們調出來的相簿顯示出中文了:
Localized resources can be mixed 設定為 YES。
1、相簿訪問許可權
許可權狀態說明
相簿、相機、通訊錄等授權狀態目前都可以對應以下幾種狀態:
AuthorizationStatusNotDetermined // 使用者從未進行過授權等處理,首次訪問相應內容會提示使用者進行授權
AuthorizationStatusAuthorized = 0, // 使用者已授權,允許訪問
AuthorizationStatusDenied, // 使用者拒絕訪問
AuthorizationStatusRestricted, // 應用沒有相關許可權,且當前使用者無法改變這個許可權,比如:家長控制
// 判斷相簿訪問許可權
- (BOOL)achiveAuthorizationStatus{
/*
* PHAuthorizationStatusNotDetermined = 0, 使用者未對這個應用程式的許可權做出選擇
* PHAuthorizationStatusRestricted, 此應用程式沒有被授權訪問的照片資料。可能是家長控制許可權。
* PHAuthorizationStatusDenied, 使用者已經明確拒絕了此應用程式訪問照片資料.
* PHAuthorizationStatusAuthorized, 使用者已授權此應用訪問照片資料.
*/
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
if (status == PHAuthorizationStatusDenied || status == PHAuthorizationStatusRestricted) {
// 沒許可權
UIAlertController *authorizationAlert = [UIAlertController alertControllerWithTitle:@"提示" message:@"沒有照片的訪問許可權,請在設定中開啟" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleCancel handler:NULL];
[authorizationAlert addAction:cancel];
[self presentViewController:authorizationAlert animated:YES completion:nil];
return NO;
} else {
return YES;
}
}
// 判斷裝置是否有攝像頭
- (BOOL) isCameraAvailable{
return [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera];
}
// 前面的攝像頭是否可用
- (BOOL) isFrontCameraAvailable{
return [UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceFront];
}
// 後面的攝像頭是否可用
- (BOOL) isRearCameraAvailable{
return [UIImagePickerController isCameraDeviceAvailable:UIImagePickerControllerCameraDeviceRear];
}
視訊錄製相關的類及作用:
AVCaptureSession
AVCaptureSession:媒體(音、視訊)捕獲會話,負責把捕獲的音視訊資料輸出到輸出裝置中。一個AVCaptureSession可以有多個輸入輸出。
AVCaptureDevice :輸入裝置,包括麥克風、攝像頭,通過該物件可以設定物理裝置的一些屬性(例如相機聚焦、白平衡等)。
AVCaptureDeviceInput :裝置輸入資料管理物件,可以根據AVCaptureDevice建立對應的AVCaptureDeviceInput物件,該物件將會被新增到AVCaptureSession中管理。
AVCaptureVideoPreviewLayer :相機拍攝預覽圖層,是CALayer的子類,使用該物件可以實時檢視拍照或視訊錄製效果,建立該物件需要指定對應的 AVCaptureSession物件。
AVCaptureOutput :輸出資料管理物件,用於接收各類輸出資料,通常使用對應的子類AVCaptureAudioDataOutput、AVCaptureStillImageOutput、
AVCaptureVideoDataOutput、AVCaptureFileOutput, 該物件將會被新增到AVCaptureSession中管理。
注意:前面幾個物件的輸出資料都是NSData型別,而AVCaptureFileOutput代表資料以檔案形式輸出,類似的,AVCcaptureFileOutput也不會直接建立使用,通常會使用其子類:
AVCaptureAudioFileOutput、AVCaptureMovieFileOutput。當把一個輸入或者輸出新增到AVCaptureSession之後AVCaptureSession就會在所有相符的輸入、輸出裝置之間
建立連線(AVCaptionConnection)。
iOS中在系統相簿中建立自己App的自定義相簿:
要建立自己App的自定義相簿,首先要獲取系統中的所有自定義相簿,看這些自定義相簿中是否已經包含了我們自己要建立的自定義相簿,如果已經包含自然不用再次建立,如果還沒有那麼就需要我們自己進行建立。注意:iOS中在建立自定義相簿之後並不會給我們返回一個相簿的物件,還需要我們自己根據一個標識去系統中獲取我們建立的自定義相簿。
程式碼:
// 建立自己要建立的自定義相簿
- (PHAssetCollection * )createCollection{
// 建立一個新的相簿
// 檢視所有的自定義相簿
// 先檢視是否有自己要建立的自定義相簿
// 如果沒有自己要建立的自定義相簿那麼我們就進行建立
NSString * title = [NSBundle mainBundle].infoDictionary[(NSString *)kCFBundleNameKey];
PHFetchResult<PHAssetCollection *> *collections = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeAlbum subtype:PHAssetCollectionSubtypeAlbumRegular options:nil];
PHAssetCollection * createCollection = nil; // 最終要獲取的自己建立的相簿
for (PHAssetCollection * collection in collections) {
if ([collection.localizedTitle isEqualToString:title]) { // 如果有自己要建立的相簿
createCollection = collection;
break;
}
}
if (createCollection == nil) { // 如果沒有自己要建立的相簿
// 建立自己要建立的相簿
NSError * error1 = nil;
__block NSString * createCollectionID = nil;
[[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
NSString * title = [NSBundle mainBundle].infoDictionary[(NSString *)kCFBundleNameKey];
createCollectionID = [PHAssetCollectionChangeRequest creationRequestForAssetCollectionWithTitle:title].placeholderForCreatedAssetCollection.localIdentifier;
} error:&error1];
if (error1) {
NSLog(@"建立相簿失敗...");
}
// 建立相簿之後我們還要獲取此相簿 因為我們要往進儲存相片
createCollection = [PHAssetCollection fetchAssetCollectionsWithLocalIdentifiers:@[createCollectionID] options:nil].firstObject;
}
return createCollection;
}
設定相機
-(void)setupCamera{
self.cameraMode = CameraModePhoto;
// 獲取攝像頭輸入裝置
if ([self cameraWithPosition:AVCaptureDevicePositionBack]) {
self.captureDevice = [self cameraWithPosition:AVCaptureDevicePositionBack];
}else if ([self cameraWithPosition:AVCaptureDevicePositionFront]){
self.captureDevice = [self cameraWithPosition:AVCaptureDevicePositionFront];
}else{
[MBProgressHUD showError:@"相機不可用"];
return;
}
NSError * error;
WEAKSELF
// 視訊輸入
self.captureInput = [AVCaptureDeviceInput deviceInputWithDevice:self.captureDevice error:&error];
if (error) {
UIAlertController *alertContr = [UIAlertController alertControllerWithTitle:@"提示" message:[error localizedDescription] preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancleAction = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
[weakSelf.navigationController dismissViewControllerAnimated:YES completion:nil];
}];
[alertContr addAction:cancleAction];
[self presentViewController:alertContr animated:YES completion:nil];
}
//音訊裝置
AVCaptureDevice *audioCaptureDevice=[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
//音訊輸入
AVCaptureDeviceInput *audioCaptureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:audioCaptureDevice error:&error];
if (error) {
WEAKSELF
UIAlertController *alertContr = [UIAlertController alertControllerWithTitle:@"提示" message:[error localizedDescription] preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancleAction = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
[weakSelf.navigationController dismissViewControllerAnimated:YES completion:nil];
}];
[alertContr addAction:cancleAction];
[self presentViewController:alertContr animated:YES completion:nil];
return;
}
//初始化輸出資料管理物件,如果要拍照就初始化AVCaptureStillImageOutput物件;如果拍攝視訊就初始化AVCaptureMovieFileOutput物件。
// 拍照圖片輸出
self.captureStillImageOutput = [[AVCaptureStillImageOutput alloc]init];
NSDictionary *outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};
[self.captureStillImageOutput setOutputSettings:outputSettings];
//視訊輸出
self.captureMovieFileOutput = [[AVCaptureMovieFileOutput alloc]init];
self.captureMovieFileOutput.movieFragmentInterval = kCMTimeInvalid;
AVCaptureConnection *captureConnection = [self.captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
if ([captureConnection isVideoStabilizationSupported ]) {
captureConnection.preferredVideoStabilizationMode=AVCaptureVideoStabilizationModeAuto;
}
// 初始化會話物件
self.captureSession = [[AVCaptureSession alloc]init];
//設定解析度 (裝置支援的最高解析度)
[self.captureSession setSessionPreset:AVCaptureSessionPresetHigh];
//給裝置新增輸入
if ([self.captureSession canAddInput:self.captureInput])
{
[self.captureSession addInput:self.captureInput];//視訊輸出
[self.captureSession addInput:audioCaptureDeviceInput];//音訊輸出
}
//將圖片輸出新增到會話中
if ([self.captureSession canAddOutput:self.captureStillImageOutput])
{
[self.captureSession addOutput:self.captureStillImageOutput];
}
//將音訊輸出新增到會話中
if ([weakSelf.captureSession canAddOutput:weakSelf.captureMovieFileOutput]) {
[weakSelf.captureSession addOutput:weakSelf.captureMovieFileOutput];
AVCaptureConnection *captureConnection=[weakSelf.captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
if ([captureConnection isVideoStabilizationSupported ]) {
captureConnection.preferredVideoStabilizationMode=AVCaptureVideoStabilizationModeAuto;
}
}
// 通過會話 (AVCaptureSession) 建立預覽層
self.preview = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];
self.preview.videoGravity=AVLayerVideoGravityResizeAspect;//填充模式
if([Helper checkCameraAuthorizationStatus] == NO){
return;
}
}
開始錄製
-(void)videoStart{
if([Helper checkCameraAuthorizationStatus] == NO){
return;
}
if([Helper checkMicAuthorizationStatus] == NO){
return;
}
self.isRecording = YES;
//修改UI顯示
self.closeBtn.hidden = YES;
self.cameraRotationBtn.hidden = YES;
self.flashLightBtn.hidden = YES;
//開始錄製
AVCaptureConnection *captureConnection=[self.captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
//預覽圖層和視訊方向保持一致
captureConnection.videoOrientation = [self.preview connection].videoOrientation;
NSString *outputFielPath=[NSTemporaryDirectory() stringByAppendingString:@"myMovie.mov"];
if ([[NSFileManager defaultManager] fileExistsAtPath:outputFielPath]) {
[[NSFileManager defaultManager] removeItemAtPath:outputFielPath error:nil];
}
NSURL *fileUrl=[NSURL fileURLWithPath:outputFielPath];
[self.captureMovieFileOutput startRecordingToOutputFileURL:fileUrl recordingDelegate:self];
//如果支援多工則則開始多工
if ([[UIDevice currentDevice] isMultitaskingSupported]) {
self.backgroundTaskIdentifier=[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
}
}
結束錄製
-(void)videoEnd{
if (self.isRecording == YES) {
self.isRecording = NO;
[_captureMovieFileOutput stopRecording];
}
}
視訊輸出代理
-(void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections{
NSLog(@"開始錄製");
}
-(void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error{
WEAKSELF
[self.videoTimer invalidate];
self.videoTimer = nil;
self.closeBtn.hidden = NO;
self.cameraRotationBtn.hidden = NO;
self.flashLightBtn.hidden = NO;
if (self.isCancelVideo == YES) {
self.isCancelVideo = NO;
NSLog(@"取消錄製 finish");
return;
}
AVURLAsset *avUrl = [AVURLAsset assetWithURL:outputFileURL];
CMTime time = [avUrl duration];
CGFloat duration = CMTimeGetSeconds(time);
NSLog(@"duration:%f",duration);
if(self.isTimeTooShort){
self.isTimeTooShort = NO;
return;
}
//視訊檔案轉換後存到本地
NSDate * now = [NSDate date];
NSString * fileName = [NSString stringWithFormat:@"%zd.mp4",[now timeIntervalSince1970] * 1000];
NSString * thumbFileName = [NSString stringWithFormat:@"%zd.jpg",[now timeIntervalSince1970] * 1000];
NSFileManager * fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString * diskCachePath = [paths[0] stringByAppendingPathComponent:@"ecamera"];
if (![fileManager fileExistsAtPath:diskCachePath]) {
[fileManager createDirectoryAtPath:diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
}
NSString * filePath =[NSString stringWithFormat:@"%@/%@",diskCachePath,fileName] ;
NSString * thumbFilePath = [NSString stringWithFormat:@"%@/%@",diskCachePath,thumbFileName];
[ToolFunction transMovToMP4:outputFileURL.absoluteString Output:filePath exportStatusHandler:^(AVAssetExportSessionStatus exportStatus) {
switch (exportStatus) {
case AVAssetExportSessionStatusFailed:
[weakSelf.recordView videoExportFailHandle];
break;
case AVAssetExportSessionStatusCancelled:
NSLog(@"匯出視訊被終了");
break;
case AVAssetExportSessionStatusCompleted:
if ([fileManager fileExistsAtPath:filePath]) {
NSLog(@"匯出視訊成功");
}else{
[weakSelf.recordView videoExportFailHandle];
}
break;
default:
break;
}
}];
NSLog(@"transMovToMP4:%f",duration);
self.filePath = filePath;
//寫資料庫
ECameraMediaModel * media = [[ECameraMediaModel alloc]init];
media.type = ECameraMediaTypeVideo;
media.fileName = fileName;
media.thumbFileName = thumbFileName;
media.thumbFilePath = thumbFilePath;
media.dateInfo = now;
media.phoneNum = @"110";
media.duration = duration;
self.tmpMedia = media;
}
通過AVAssetExportSeeion 這個類對視訊進行壓縮
// 壓縮視訊
- (IBAction)compressVideo:(id)sender
{
NSString *cachePath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *savePath=[cachePath stringByAppendingPathComponent:MOVIEPATH];
NSURL *saveUrl=[NSURL fileURLWithPath:savePath];
// 通過檔案的 url 獲取到這個檔案的資源
AVURLAsset *avAsset = [[AVURLAsset alloc] initWithURL:saveUrl options:nil];
// 用 AVAssetExportSession 這個類來匯出資源中的屬性
NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];
// 壓縮視訊
if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality]) { // 匯出屬性是否包含低解析度
// 通過資源(AVURLAsset)來定義 AVAssetExportSession,得到資源屬性來重新打包資源 (AVURLAsset, 將某一些屬性重新定義
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:AVAssetExportPresetLowQuality];
// 設定匯出檔案的存放路徑
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd-HH:mm:ss"];
NSDate *date = [[NSDate alloc] init];
NSString *outPutPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true) lastObject] stringByAppendingPathComponent:[NSString stringWithFormat:@"output-%@.mp4",[formatter stringFromDate:date]]];
exportSession.outputURL = [NSURL fileURLWithPath:outPutPath];
// 是否對網路進行優化
exportSession.shouldOptimizeForNetworkUse = true;
// 轉換成MP4格式
exportSession.outputFileType = AVFileTypeMPEG4;
// 開始匯出,匯出後執行完成的block
[exportSession exportAsynchronouslyWithCompletionHandler:^{
// 如果匯出的狀態為完成
if ([exportSession status] == AVAssetExportSessionStatusCompleted) {
dispatch_async(dispatch_get_main_queue(), ^{
// 更新一下顯示包的大小
self.videoSize.text = [NSString stringWithFormat:@"%f MB",[self getfileSize:outPutPath]];
});
}
}];
}
}
拍照
-(void)photoBtnClick{
if([Helper checkCameraAuthorizationStatus] == NO){
return;
}
//根據裝置輸出獲得連線
AVCaptureConnection *captureConnection=[self.captureStillImageOutput connectionWithMediaType:AVMediaTypeVideo];
//根據連線取得裝置輸出的資料
WEAKSELF
[self.captureStillImageOutput captureStillImageAsynchronouslyFromConnection:captureConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
if (imageDataSampleBuffer) {
NSData *imageData=[AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
UIImage *imageTemp = [UIImage imageWithData:imageData];
UIImage *image = [ToolFunction cropImage:imageTemp Rect:weakSelf.view.frame];
image = [image fixOrientation];
if([weakSelf.preview connection].videoOrientation == AVCaptureVideoOrientationLandscapeLeft){
image = [UIImage image:image rotation:UIImageOrientationRight];
}else if([weakSelf.preview connection].videoOrientation== AVCaptureVideoOrientationLandscapeRight){
image = [UIImage image:image rotation:UIImageOrientationLeft];
}else if([weakSelf.preview connection].videoOrientation == AVCaptureVideoOrientationPortraitUpsideDown){
image = [UIImage image:image rotation:UIImageOrientationDown];
}
//儲存到相簿
//UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
//寫入檔案
NSDate * now = [NSDate date];
NSString * fileName = [NSString stringWithFormat:@"%zd.jpg",[now timeIntervalSince1970] * 1000];
NSFileManager * fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString * diskCachePath = [paths[0] stringByAppendingPathComponent:@"ecamera"];
if (![fileManager fileExistsAtPath:diskCachePath]) {
[fileManager createDirectoryAtPath:diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
}
NSString * filePath =[NSString stringWithFormat:@"%@/%@",diskCachePath,fileName] ;
[UIImageJPEGRepresentation(image, 0.75) writeToFile:filePath atomically:YES];
//寫入資料庫
ECameraMediaModel * media = [[ECameraMediaModel alloc]init];
media.type = ECameraMediaTypeImage;
media.fileName = fileName;
media.dateInfo = now;
media.phoneNum = @"110";
[ECameraSQL insertEasyCameraMediaWith:media];
//儲存到自定義相簿
[ToolFunction saveToAlbumWithMetadata:nil imageData:UIImagePNGRepresentation(image) customAlbumName:@"樂魚" completionBlock:^{
//[MBProgressHUD showSuccess:@"儲存成功"];
} failureBlock:^(NSError *error) {
[MBProgressHUD showError:@"儲存失敗"];
}];
weakSelf.photoView.thumbnailImgV.image = image;
weakSelf.recordView.thumbImgV.image = image;
CGFloat kAnimationDuration = 0.3f;
CAGradientLayer *contentLayer = (CAGradientLayer *)weakSelf.photoView.thumbnailImgV.layer;
CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
scaleAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0, 0, 1)];
scaleAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1)];
scaleAnimation.duration = kAnimationDuration;
scaleAnimation.cumulative = NO;
scaleAnimation.repeatCount = 1;
[scaleAnimation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]];
[contentLayer addAnimation: scaleAnimation forKey:@"myScale"];
}
}];
}
參考過的文章:https://www.jianshu.com/p/7c57c58c253d
http://kayosite.com/ios-development-and-detail-of-photo-framework-part-two.html
https://123sunxiaolin.github.io/2016/08/27/iOS%E4%B8%AD%EF%BC%8C%E7%B3%BB%E7%BB%9F%E7%9B%B8%E5%86%8C%E7%9A%84%E9%82%A3%E4%BA%9B%E4%BA%8B/
相關文章
- iOS開發 AVFoundation 自定義視訊錄製iOS
- Camera2錄製視訊(一):音訊的錄製及編碼音訊
- iOS採集錄製音視訊API選擇推薦iOSAPI
- 音視訊入門之音訊採集、編碼、播放音訊
- 短視訊直播原始碼,自定義圖片或視訊的迴圈播放原始碼
- 專業的視訊編輯工具:視訊編輯軟體
- 短視訊開發app,自定義帶進度條的視訊播放按鈕APP
- Filmage Screen for Mac(螢幕錄製和視訊編輯軟體)Mac
- android短視訊開發,自定義下拉選單Android
- 小視訊原始碼,在編輯器中新增自定義的圖片原始碼
- 短視訊開發app,webservice自定義加入攔截器APPWeb
- 短視訊seo優化,短視訊seo排名優化
- iOS開發:音訊播放、錄音、視訊播放、拍照、視訊錄製iOS音訊
- 音訊錄製及視覺化處理音訊視覺化
- 自定義視訊播放器播放器
- Android音視訊之MediaRecorder音視訊錄製Android
- 短視訊直播系統開發直播短視訊程式搭建短視訊互動直播
- iOS視訊流採集概述(AVCaptureSession)iOSAPTSession
- iOS視訊採集實戰(AVCaptureSession)iOSAPTSession
- iOS - 直播系列一:視訊採集iOS
- Windows Media Foundation 音視訊採集Windows
- 短視訊“音訊化”,音樂“視訊化”音訊
- android短視訊錄製與頭像跟隨(一)Android
- 短視訊平臺搭建,自定義滾動條的樣式
- 短視訊平臺原始碼,自定義流式佈局--kotlin原始碼Kotlin
- 直播短視訊原始碼短視訊APP短視訊帶貨系統多商戶直播原始碼APP
- 安卓手機怎麼錄製直播視訊安卓
- iOS開發系列--音訊播放、錄音、視訊播放、拍照、視訊錄製(轉)iOS音訊
- RecorderManager安卓仿微信自定義音視訊錄製第三方庫安卓
- Linux下錄製視訊Linux
- 最快1天搭建短視訊APP!阿里雲短視訊解決方案上線APP阿里
- 音視訊系列之iOS: 音訊採集 AudioUnitiOS音訊
- Android 音視訊採集那些事Android
- 短視訊平臺搭建,Android自定義旋轉進度條Android
- 短視訊帶貨原始碼,android 自定義常駐通知欄原始碼Android
- 騰訊又出短視訊小程式“看一看短視訊”。短視訊原始碼有巨大潛力?原始碼
- Linux下使用"avconv"捕捉錄製桌面視訊及音訊Linux音訊
- 視訊回放編輯工具:Mitti MacMITMac