كيفية إنشاء كاميرا متحركة لإيقاف الحركة باستخدام AVFoundation في iOS

يُعد إنشاء الكاميرا أحد المهام الشائعة في تطوير تطبيق iOS للوسائط الاجتماعية. في هذه المقالة ، سنقوم ببناء تطبيق بكاميرا لإنشاء تأثير الحركة إيقاف الحركة. يتم تحقيق تأثير إيقاف الحركة من خلال تشغيل سلسلة من الصور كتسلسل سريع لإنشاء وهم بالحركة.

تم إنشاء تطبيق Stop-Motion باستخدام AVFoundation - في نهاية البرنامج التعليمي ، يجب أن يكون لديك تطبيق يقوم بذلك :)
بادئ ذي بدء ، يمكن إنشاء تطبيق iOS مع وظائف الكاميرا بطريقتين مختلفتين. إما باستخدام UIImagePickerController أو من خلال إطار AVFoundation.

ما هو AVFoundation؟

AVFoundation هو إطار يعمل بنظام iOS يستخدم لبناء وظائف السمعية والبصرية في تطبيقك. باستخدام هذا الإطار ، يمكنك معالجة التقاط ومعالجة وتحرير واستيراد وتصدير أصول الصوت والفيديو.

UIImagePickerController vs AVFoundation:

من خلال UIImagePickerController ، يمكننا تنفيذ جميع المهام الأساسية مثل الالتقاط ، تبديل الفلاش ، تبديل الكاميرا ، تحرير التركيز والتعرض. كما أنه يتيح لك الوصول إلى مكتبة الصور لتخزين الصور ومشاركتها. إنه أسهل في التنفيذ مقارنةً بـ AVFoundation.

ومع ذلك ، يوفر AVFoundation تحكمًا كاملاً في إعدادات الكاميرا ومعالجة الوسائط. يتيح لك القيام بجميع المهام الأساسية مثل UIImagePickerController بالإضافة إلى أنه يمكنك معالجة البيانات الخام من الالتقاط ، والتشغيل ، وإنشاء صور مصغرة / ثابتة من الفيديو ، وتحرير وتغيير الإعدادات مثل التركيز ، والتعرض ، إلخ لالتقاط الوسائط الثابتة والفيديو.

كيفية استخدام AVFoundation؟

AVFoundation لديه 5 فئات رئيسية:

  • AVCaptureDevice: يمثل جهاز التقاط فعلي مثل الكاميرا والميكروفون. لكل iPhone ، يوجد ميكروفون واحد (إدخال الصوت) وكاميرا (أمامية وخلفية للإدخال المرئي). يمكنك تكوين إعدادات جهاز الإدخال باستخدام هذا قبل تمريره إلى جلسة الالتقاط.
  • AVCaptureDeviceInput: يوفر الوسائط من جهاز الالتقاط إلى جلسة التقاط.
  • AVCaptureOutput: يمثل الإخراج من جهاز الالتقاط. يمكن أن يكون الإخراج من الالتقاط أحد الخيارات التالية - AVCaptureMovieFileOutput أو AVCaptureVideoDataOutput أو AVCaptureAudioDataOutput أو AVCapturePhotoOutput. يمثل AVCapturePhotoOutput إخراج صورة ثابت. يمكننا تكوين إعدادات الإخراج مثل الإعداد المسبق وتنسيق البيانات وإعدادات تمثيل البيانات الخام لـ AVCapturePhotoOutput.
  • AVCaptureSession: يعمل منسقًا بين مدخلات ومخرجات AV. يمكنك تهيئة جلسة التقاط ، إضافة المدخلات والمخرجات لالتقاط إلى الجلسة. ثم تبدأ تشغيل الجلسة لإنشاء تدفق البيانات بين المدخلات والمخرجات.
  • AVCaptureVideoPreviewLayer: بصرف النظر عن ما سبق ، لتزويد المستخدم بمعاينة في الوقت الفعلي لما يتم التقاطه أو تسجيله ، يتم استخدام هذا.

الآن ، مع هذه الأساسيات ، لنبدأ في إنشاء التطبيق:

لتحقيق تأثير حركة الإيقاف ، سنسمح للمستخدم بالتقاط سلسلة من الصور وإنشاء ملف GIF باستخدام إطار AVFoundation. سيتطلب التطبيق فئتين UIViewController وفئة لاستخلاص جميع وظائف الكاميرا التي سنستخدمها:

  • ViewController - وحدة تحكم العرض الأولي الافتراضية التي يتم إنشاؤها عند إنشاء تطبيق عرض واحد جديد. سوف نستخدم هذا لإظهار الكاميرا الحية مع أزرار لتغيير إعدادات الكاميرا ، والتقاط صورة وإنشاء وسائط إيقاف الحركة.
  • PreviewViewController —يعرض معاينة حركة الإيقاف مع خيار لحفظها في مكتبة الصور.
  • CameraSetup - تجريد على جميع وظائف الكاميرا التي نستخدمها. في هذه الفئة ، سنستخدم إطار عمل AVFoundation. سوف تحتوي فئة CameraViewController على مثيل لـ CameraSetup.

TL: DR يمكنك العثور على رمز العمل في مستودع git الخاص بي.

  1. لنبدأ بإعداد لوحة القصة. إنشاء مشروع لتطبيق عرض واحد. في ViewController الأولي ، ما عليك سوى إضافة عرض آخر وتعيين حوافه إلى حواف superview. أضف 4 أزرار في المجموع كما هو موضح أدناه - تبديل الكاميرا ، وتحول الفلاش ، والتقاط وزر Done لإنشاء GIF لوقف الحركة من مجموعة الصور الملتقطة.

2. قم بإنشاء ملف فئة لمس الكاكاو جديد وهو فئة فرعية من UIViewController. أطلق عليها اسم "PreviewViewController" لأن هذا سيُظهر المعاينة النهائية لـ GIF stop stop التي تم إنشاؤها.

3. إضافة وحدة تحكم عرض أخرى إلى لوحة العمل. قم بتضمين عرض صورة واضبط حوافها على طريقة العرض superview. أضف زرين هنا - أحدهما لحفظ GIF في مكتبة الصور والآخر هو الإغلاق بدون الحفظ. في مفتش الهوية الخاص به ، حدد الفئة كـ PreviewViewController.

4. قم الآن بإنشاء شريحة من ViewController إلى PreviewViewController وفي مفتش السمات الخاص بها ، قم بتعيين معرف السلسلة على "showPreview". سوف نستخدم هذا في التعليمات البرمجية لإجراء الانتقال من وحدة تحكم العرض الأولى إلى الثانية.

5. قم بإنشاء فصل سريع جديد لـ "CameraSetup" الذي سنقوم فيه بتنفيذ AVFoundation. وسنقوم بتهيئة كائن فئة في ViewController لتنفيذ إجراءات الكاميرا.

دعنا نركز على فئة CameraSetup حيث سنستخدم AVFoundation.

استيراد AVFoundation
Class CameraSetup {
....
}

في ملف الفصل ، دعنا نعلن أولاً قائمة المتغيرات التي نحتاجها.

/ / تهيئة جلسة التقاط الكاميرا
var captureSession = AVCaptureSession ()
// قائمة جهاز الالتقاط المطلوب لالتقاط صورة ثابتة
فار frontCam: AVCaptureDevice؟
فار RearCam: AVCaptureDevice؟
فار currentCam: AVCaptureDevice؟
// المدخلات والمخرجات لالتقاط
var captureInput: AVCaptureDeviceInput؟
var captureOutput: AVCapturePhotoOutput؟
// طبقة معاينة الكاميرا
var معاينة الطبقة: AVCaptureVideoPreviewLayer؟

الآن يتيح إنشاء بعض الوظائف لتكوين وتشغيل جلسة الالتقاط. لنبدأ مع CaptureDevice

func captureDevice () {
واسمحوا discoverySession = AVCaptureDevice.DiscoverySession (deviceTypes: [.builtInWideAngleCamera]، mediaType: AVMediaType.video، position: .unspecified)
        من أجل d in discoverySession.devices {
            إذا d.position == .front {
                frontCam = د
            }
            آخر إذا d.position == .back {
                RearCam = د
                فعل{
                    جرب RearCam؟. lockForConfiguration ()
                    RearCam؟ .focusMode = .autoFocus
                    RearCam؟ .exposureMode = .autoExpos
                    rearCam؟ .unlockForConfiguration ()
                }
                catch let error {
                    طباعة (خطأ)
                }
            }
        }
    }

باستخدام استعلام DiscoverySession ، يوفر لنا قائمة بأجهزة الإدخال الفعلي التي تطابق المعايير المحددة. نحن هنا نريد قائمة بالأجهزة المزودة بكاميرا بزاوية عريضة مدمجة يمكنها أن تأخذ وسائط مرئية. ثم نقوم بتعيين الجهاز على frontCam إذا كان وضع الجهاز في المقدمة وينطبق الشيء نفسه على العمق.

مع RearCam ، قمت بتكوين وضع التركيز والتعرض. هذه بعض الميزات التي يمكن تكوينها باستخدام AVFoundation أثناء إعداد الكاميرا لالتقاطها.

لدينا frontCam و rearCam التي يمكن أن نضيفها كمدخلات Capture. يتيح إنشاء وظيفة لتكوين الالتقاط ، لإضافة مدخلات إلى الجلسة.

تكوين funcaptureInput () {
        currentCam = RearCam!
        فعل{
            captureInput = try AVCaptureDeviceInput (device: currentCam!)
            إذا captureSession.canAddInput (captureInput!) {
                captureSession.addInput (captureInput!)
            }
        }
        catch let error {
            طباعة (خطأ)
        }
    }

هنا ، نتحقق مما إذا كان يمكن إضافة المدخلات إلى الجلسة ، وإذا كان ذلك صحيحًا ، فسنضيفها. وبالمثل ، نحتاج إلى تكوين CaptureOutput وإضافته إلى الجلسة. لذلك دعونا نضيف الوظيفة التالية إلى الفصل ،

func configCaptureOutput () {
captureOutput = AVCapturePhotoOutput ()
        captOutput!
        إذا captureSession.canAddOutput (captureOutput!) {
            captureSession.addOutput (captureOutput!)
        }
        captureSession.startRunning ()
}

setPreparedPhotoSettingsArray يحدد إعداد إخراج الصورة لرغبتنا. بمجرد إضافة الالتقاط إلى الجلسة ، حان الوقت لبدء الجلسة. لهذا الغرض ، نسميه startRunning ().

أخيرًا ، ما تبقى من الفصول الخمسة هو AVVideoPreviewLayer. يتيح المضي قدما وإضافة هذا الجزء الأخير.

func configPreviewLayer (عرض: UIView) {
  previewLayer = AVCaptureVideoPreviewLayer (الجلسة: captureSession)
  
  previewLayer؟ .videoGravity = AVLayerVideoGravity.resizeAspectFill
  previewLayer؟ .connection؟ .videoOrientation = .portrait
        
  view.layer.insertSublayer (previewLayer! ، على: 0)
  previewLayer؟ .frame = view.frame
 }

لهذه الوظيفة ، سنتجاوز UIView التي أنشأناها في ViewController الأول. AVCaptureVideoPreviewLayer هي فئة فرعية من CALayer (Core Animation) وتعمل بشكل متزامن مع الالتقاط المعطى.

6. انتقل إلى ViewController ، وقم بتوصيل العرض والزر وإجراءاته بوحدة التحكم في العرض من لوحة القصة. سيبدو شيء هكذا،

إضافة وظيفة تسمى تهيئة لاستدعاء جميع وظائف التكوين من CameraSetup.

var cameraSetup: CameraSetup!
...
تهيئة func () {
        cameraSetup = CameraSetup ()
        cameraSetup.captureDevice ()
        cameraSetup.configureCaptureInput ()
        cameraSetup.configureCaptureOutput ()
        cameraSetup.configurePreviewLayer (عرض: camView)
    }

ملاحظة: لا يمكنك تشغيل هذا في جهاز محاكاة. قم بتشغيل التطبيق على جهاز iphone الخاص بك. يجب أن تكون قادرًا على رؤية المعاينة.

لاحظ أنه عند النقر فوق زر الالتقاط ، لا يحدث شيء. هذا لأننا لم نقم بعد بإعداد هذه الوظيفة. لذلك دعونا نضيف ذلك الآن في وحدة تحكم العرض.

التقاط صورة هو عمل غير متزامن. هذا يعني أنه يجب عليك تمرير وظيفة رد الاتصال إلى AVFoundation التي سيتم استدعاؤها بمجرد التقاط الصورة.
var معاينة الصورة = [UIImage] ()
...
IBAction func captureAction (_ المرسل: أي) {
        cameraSetup.captureImage {(الصورة ، الخطأ) في
            حراسة السماح للصورة = صورة أخرى {
                طباعة (خطأ؟ "خطأ في التقاط الصورة")
                إرجاع
            }
            self.previewImage.append (صورة)
        }
}

نقوم بإنشاء مجموعة UIImage لتخزين جميع الصور الملتقطة لإنشاء وقف الحركة. في captureAction نسمي وظيفة captureImage من CameraSetup مع إغلاق كمعلمة. ثم يتم حفظ الصورة التي تم إرجاعها في هذا الإغلاق في الصفيف.

في فئة CameraSetup ، أضف الوظيفة التالية

فار photoCaptureCompletionBlock: ((UIImage؟ ، خطأ؟) -> باطل)؟
...
func captureImage (إكمال:escaping (UIImage؟ ، خطأ؟) -> باطل) {
        دع الإعدادات = AVCapturePhotoSettings ()
        settings.flashMode = self.flashMode
        
        self.captureOutput؟ .capturePhoto (مع: الإعدادات ، المفوض: self as AVCapturePhotoCaptureDelegate)
        self.photoCaptureCompletionBlock = الإكمال
    }

photoCaptureCompletionBlock هو اسم الإغلاق المحدد في الالتقاط لدينا.

يتم استخدام capturePhoto التقاط صورة ثابتة مع الإعدادات المحددة للصورة مثل خيار الفلاش وتنسيق البيانات وما إلى ذلك. تتطلب هذه المكالمة AVCapturePhotoCaptureDelegate ليتم تنفيذها. لاحظ أننا قد جعلنا نفسه مفوضًا وبالتالي يتيح إضافة وظيفة لمتابعة بروتوكول المفوض.

func public photoOutput (_ output: AVCapturePhotoOutput، didFinishProcessingPhoto photo: AVCapturePhoto، error: Error؟) {
        إذا سمحت x = خطأ {
            self.photoCaptureCompletionBlock؟ (لا شيء ، x)
        }
        وإلا إذا سمحت البيانات = photo.fileDataRepresentation () ، فدع الصورة = UIImage (البيانات: البيانات) {
            self.photoCaptureCompletionBlock؟ (صورة ، لا شيء)
        }
    }

توفر لنا صورة didFinishProcessingPhoto صورة نهائية معالجة والتي سنقوم بتخزينها في صفيفنا.

7. ستشمل وظيفة زر Done في ViewController إعداد وتنفيذ المجموعة. تذكر "المجموعة" showPreview التي أنشأناها ، والتي تسمى هنا عند النقر على زر تم. تعريف زر تم ،

IBAction func doneAction (_ المرسل: أي) {
    self.performSegue (withIdentifier: "showPreview" ، المرسل: self)
    }
overcide func preparation (لـ segue: UIStoryboardSegue ، المرسل: أي؟) {
   إذا دعنا الوجهة = segue.destination كما؟ PreviewViewController {
            destination.pImg = self.previewImage
        }
    }

8. افتح PreviewViewController الخاص بك ، قم بتوصيل UIImageView بوحدة تحكم العرض. إنشاء مجموعة UIImage تسمى "pImg" وجهاز توقيت. في viewDidLoad () أضف ما يلي.

تجاوز func viewDidLoad () {
  super.viewDidLoad ()
  المعاينة. الصورة = pImg [0]
  timer = Timer.scheduledTimer (timeInterval: 0.5، target: self، selector: #selector (self.display)، userInfo: nil، repeats: true)
    }
    
    objc func display () {
        if! (pImg.count == counter) {
            معاينة. صورة = pImg [عداد]
            عداد + = 1
        }
        آخر
            عداد = 0
        }
    }

يتم إنشاء مؤقت بسيط لتشغيل معاينة الصور المحددة في حلقة باستخدام العرض ().

نحن على وشك الإنتهاء. يتيح إضافة كتل العمل لزر الحفظ والإغلاق. أضف closeAction () حيث سنبطل الموقت ونرفض PreviewViewController.

IBAction func closeAction (_ المرسل: أي) {
        timer.invalidate ()
        صرف (الرسوم المتحركة: صحيح ، الانتهاء: لا شيء)
    }

الآن لحفظ مجموعة الصور كملف GIF في مكتبة الصور ، قم بما يلي

IBAction func saveAction (_ المرسل: أي) {
        createGIF (الصور: pImg)
        صرف (الرسوم المتحركة: صحيح ، الانتهاء: لا شيء)
    }

تقوم دالة createGIF بإنشاء GIF باستخدام kCGImagePropertyGIFDictionary ، ثم تقوم بإنشاء عنوان URL يحتوي على بيانات الصورة. من عنوان URL هذا ، باستخدام PHPhotoLibrary إنشاء وإضافة مادة إلى مكتبة الصور. (كمرجع)

استيراد الصور
...
func createGIF (الصور: [UIImage]) {
        دع fileProperties: CFDictionary = [kCGImagePropertyGIFDictionary مثل السلسلة: [kCGImagePropertyGIFLoopCount كـ String: 0]] كـ CFDictionary
        اسمحوا frameProperties: CFDictionary = [kCGImagePropertyGIFDictionary مثل السلسلة: [kCGImagePropertyGIFUnclampedDelayTime as String: 0.5]] كـ CFDictionary
        
        واسمحوا الوثائقالدليل: URL؟ = حاول؟ FileManager.default.url (لـ: .documentDirectory ، في: .userDomainMask ، المناسبةFor: nil ، create: true)
        واسمحوا fileURL: URL؟ = documentsDirectoryURL؟ .appendingPathComponent ("animated.gif")
        إذا اسمح لـ url = fileURL كـ CFURL؟ {
            إذا سمحت الوجهة = CGImageDestinationCreateWithURL (url، kUTTypeGIF، images.count، nil) {
  CGImageDestinationSetProperties (الوجهة ، خصائص الملف)
للصور في الصور {
 إذا سمح cgImage = image.cgImage {
  CGImageDestinationAddImage (الوجهة ، cgImage ، FrameProperties)
 }
}
if! CGImageDestinationFinalize (الوجهة) {
   طباعة ("فشل في إنهاء وجهة الصورة")
}
                طباعة ("Url = \ (fileURL!)")
}
}
 
// طلب إنشاء أصل من الصورة وحفظه إلى
// مكتبة الصور.
PHPhotoLibrary.shared (). performChanges ({PHAssetChangeRequest.creationRequestForAssetFromImage (atFileURL: fileURL!)
        })
    }

ونحن انتهينا.

كل ما تبقى الآن هو تبديل الكاميرا ووظائف الفلاش الاختيارية. للتبديل بين الكاميرا ، إذا كان للجلسة RearCam ، فنحن نزيل جميع المدخلات من الجلسة ونضيف frontCam كالتقاط جديد ونجعل CurrentCam كـ frontCam والعكس صحيح.

func toggleCam () {
        captureSession.beginConfiguration ()
        دع newCam = (currentCam؟ .position == .front)؟ RearCam: frontCam
        
        للإدخال في captureSession.inputs {
            captureSession.removeInput (الإدخال كـ! AVCaptureDeviceInput)
        }
        
        currentCam = newCam
        فعل{
            captureInput = try AVCaptureDeviceInput (device: currentCam!)
            إذا captureSession.canAddInput (captureInput!) {
                captureSession.addInput (captureInput!)
            }
        }
        catch let error {
            طباعة (خطأ)
        }
        
        captureSession.commitConfiguration ()
    }

في وحدة تحكم العرض للكاميرا Toggle ، اتصل بهذا func من CameraSetup ،

IBAction func cameraToggle (_ المرسل: أي) {
        cameraSetup.toggleCam ()
    }

وبالمثل بالنسبة لتبديل الفلاش ، فقط قم بتغيير قيمة المتغير flashMode.

IBAction func flashToggle (_ المرسل: أي) {
 إذا cameraSetup.flashMode == .off {
     flashButton.setImage (UIImage (المسمى: "flash_on") ، من أجل: .normal)
     cameraSetup.flashMode =
 }
 آخر
     flashButton.setImage (UIImage (المسمى: "flash_off") ، من أجل: .normal)
      cameraSetup.flashMode = .off
 }
}

هذا كل شيء لدينا الآن تطبيق وظيفي كامل. قم بتشغيل التطبيق. يجب أن يكون الإخراج مثل ،

المشروع الكامل متاح عبر رابط جيثب المذكور أدناه. أرحب بجميع الملاحظات وأي أسئلة. قم بإنشاء الكاميرا المخصصة الخاصة بك الآن باستخدام AVFoundation. ترميز سعيد .. :)