Follow me
RSS feed
My sources
My Viadeo

Capture vidéo avec MacRuby

Greg | 29 Mar 2010

Dev Dans la série à la découverte de MacRuby, nous allons voir aujourd'hui comment jouer avec le QTKit. Pour cela, nous allons utiliser ce framework afin de capturer le flux vidéo provenant de la caméra intégrée du Mac, le projeter dans une vue et l'enregistrer dans un fichier :

QTKit MacRuby

Le QTKit

L'utilisation du QTKit est beaucoup plus facile qu'il n'y parait. Dans le principe il nous suffit de créer une instance de QTCaptureSession, de lui attacher en entrée (via QTCaptureDeviceInput) un device créé avec QTCaptureDevice et de renvoyer le tout vers une vue (QTCaptureView) et un fichier (QTCaptureMovieFileOutput) :

QTKit MacRuby

Capturer et afficher

Nous allons commencer très simplement en capturant le flux et en l'affichant dans une vue.

Nous créons donc un nouveau projet de type MacRuby Application dans XCode. Comme nous avons besoin du framework QTKit, nous allons tout de suite l'ajouter à notre projet. Pour mémoire, il suffit de faire un clic droit sur l'entrée Add > Existing Frameworks... du menu contextuel de l'entrée Linked Frameworks de la zone Groups and Files. Dans le panneau qui s'affiche, sélectionnez QTKit.framework et cliquez sur Add.

Nous allons maintenant ajouter une classe AppController à notre projet. Dans cette classe, nous allons démarrer la capture dès le lancement de l'application. Le bon emplacement pour cela est donc la méthode awakeFromNib. Dans cette méthode nous allons nous contenter de suivre à la lettre le déroulement du schéma ci-dessus en ignorant, pour le moment, la partie qui concerne l'écriture du flux dans un fichier.

Comme nous avons besoin d'une vue pour l'affichage du flux vidéo dans la fenêtre de notre application, nous pouvons commencer par mettre en place un Outlet pour y accéder :

AppController.rb
# AppController.rb
# QTRuby
#
# Created by greg on 29/03/10.
# Copyright 2010 Grégoire Lejeune. All rights reserved.

framework 'QTKit'

class AppController
  attr_accessor :outputView

  def awakeFromNib
    # ...
  end
end

Dans la méthode awakeFromNib, nous commençons par créer le device :

captureDevice = QTCaptureDevice.defaultInputDeviceWithMediaType(QTMediaTypeVideo)
captureDevice.open(nil)

Comme vous pouvez le voir, nous créons le device en déclarant qu'il s'agit d'un média de type vidéo (QTMediaTypeVideo). Je vous laisse imaginer ce qu'il en aurait été si nous avions voulu capturer le flux audio...

Nous déclarons ensuite ce device comme étant le device d'entrée.

@inputDevice = QTCaptureDeviceInput.deviceInputWithDevice(captureDevice)

Nous créons la session en lui rattachant le device d'entrée.

@captureSession = QTCaptureSession.alloc.init
@captureSession.addInput(@inputDevice, error:nil)

Nous renvoyons le tout vers la vue.

@outputView.setCaptureSession(@captureSession)

Et nous terminons en démarrant la session.

@captureSession.startRunning()

Nous allons nous arrêter là pour le moment, en prenant le temps de tester ce que nous avons déjà mis en place. Pour cela nous allons mettre en place l'interface graphique :

Vous pouvez maintenant exécuter ce que nous avons déjà fait. Facile !

Sauvegarde dans un fichier

La sauvegarde dans un fichier ne va pas nous demander beaucoup plus d'efforts. Cependant, avant de mettre cela en place, il peut être bon de terminer ce que nous avons commencé. En effet, à aucun moment nous ne fermons le flux vidéo. Bien entendu, il est fermé lorsque nous quittons l'application. Mais de manière contrainte et forcée. Pour être propre, je vous propose de modifier notre code en utilisant la classe AppController comme délégué de la fenêtre de l'application et en ajoutant la fermeture du flux dans la méthode windowWillClose :

AppController.rb
# AppController.rb
# QTRuby
#
# Created by greg on 29/03/10.
# Copyright 2010 Grégoire Lejeune. All rights reserved.

framework 'QTKit'

class AppController
  attr_accessor :outputView
  
  def awakeFromNib
    @recording = false
    
    captureDevice = QTCaptureDevice.defaultInputDeviceWithMediaType(QTMediaTypeVideo)
    captureDevice.open(nil)

    @inputDevice = QTCaptureDeviceInput.deviceInputWithDevice(captureDevice)
    
    @captureSession = QTCaptureSession.alloc.init
    @captureSession.addInput(@inputDevice, error:nil)
    
    @outputView.setCaptureSession(@captureSession)
    
    @captureSession.startRunning()
  end
  
  def windowWillClose(notification)
    @captureSession.stopRunning()
    @inputDevice.device.close()
  end
end

Comme nous l'avons vu sur le schéma un peu plus haut, mettre en place la sauvegarde du flux dans un fichier demande simplement d'ajouter une nouvelle sortie à la session de type QTCaptureMovieFileOutput. Il suffit donc d'ajouter les deux lignes suivantes dans la méthode awakeFromNib, avant de démarrer la session :

@outputMovieFile = QTCaptureMovieFileOutput.alloc.init
@captureSession.addOutput(@outputMovieFile, error:nil)

Par la suite, nous déclencherons la sauvegarde du flux en utilisant la méthode recordToOutputFileURL: de l'objet de type QTCaptureMovieFileOutput. Pour stopper la sauvegarde, nous utiliserons la même méthode en lui passant nil comme paramètre.

Nous allons donc modifier notre interface de manière à lui ajouter un bouton permettant de démarrer/arrêter la capture. Le bouton servant pour les deux actions, nous avons besoin d'un Outlet qui nous permettra de modifier son titre selon que nous sommes ou non en train d'enregistrer le flux. Nous allons également utiliser une variable de classe @recording comme marqueur d'état ; il sera à true si nous somme en train d'enregistrer et à false sinon. Voici donc la nouvelle version de notre contrôleur :

AppController.rb
# AppController.rb
# QTRuby
#
# Created by greg on 29/03/10.
# Copyright 2010 Grégoire Lejeune. All rights reserved.

framework 'QTKit'

class AppController
  attr_accessor :outputView, :startStopButton
  
  def awakeFromNib
    @recording = false
    
    captureDevice = QTCaptureDevice.defaultInputDeviceWithMediaType(QTMediaTypeVideo)
    captureDevice.open(nil)

    @inputDevice = QTCaptureDeviceInput.deviceInputWithDevice(captureDevice)
    
    @captureSession = QTCaptureSession.alloc.init
    @captureSession.addInput(@inputDevice, error:nil)
    
    @outputView.setCaptureSession(@captureSession)
    
    @outputMovieFile = QTCaptureMovieFileOutput.alloc.init
    @captureSession.addOutput(@outputMovieFile, error:nil)
    
    @captureSession.startRunning()
  end
  
  def windowWillClose(notification)
    @captureSession.stopRunning()
    @inputDevice.device.close()
  end
  
  def startStopRecording(sender)
    if @recording
      @outputMovieFile.recordToOutputFileURL(nil)
      @startStopButton.setTitle("Start recording")
      @recording = false
    else
      @outputMovieFile.recordToOutputFileURL(NSURL.fileURLWithPath("/Users/greg/Desktop/test.mov"))
      @startStopButton.setTitle("Stop recording")
      @recording = true
    end
  end  
end

Nous pouvons maintenant modifier l'interface :

Il nous reste une dernière petite chose à faire : permettre de choisir le fichier dans lequel doit être sauvegardé le flux. Pour cela je vais faire un peu roots. Nous allons utiliser un champ texte (NSTextField) et un bouton. L'action derrière le bouton ouvrira un NSSavePanel. Nous placerons le fichier chemin du fichier choisi via le panneau dans le champ texte. Enfin, dans startStopRecording: nous utiliserons la valeur de ce champ comme nom pour le fichier de sortie.

AppController.rb
# AppController.rb
# QTRuby
#
# Created by greg on 29/03/10.
# Copyright 2010 Grégoire Lejeune. All rights reserved.

framework 'QTKit'

class AppController
  attr_accessor :outputView, :startStopButton, :fileName
  
  def awakeFromNib
    @recording = false
    
    captureDevice = QTCaptureDevice.defaultInputDeviceWithMediaType(QTMediaTypeVideo)
    captureDevice.open(nil)

    @inputDevice = QTCaptureDeviceInput.deviceInputWithDevice(captureDevice)
    
    @captureSession = QTCaptureSession.alloc.init
    @captureSession.addInput(@inputDevice, error:nil)
    
    @outputView.setCaptureSession(@captureSession)
    
    @outputMovieFile = QTCaptureMovieFileOutput.alloc.init
    @captureSession.addOutput(@outputMovieFile, error:nil)
    
    @captureSession.startRunning()
  end
  
  def windowWillClose(notification)
    @captureSession.stopRunning()
    @inputDevice.device.close()
  end
  
  def startStopRecording(sender)
    if @recording
      @outputMovieFile.recordToOutputFileURL(nil)
      @startStopButton.setTitle("Start recording")
      @recording = false
    else
      if @fileName.stringValue.size > 0
        @outputMovieFile.recordToOutputFileURL(NSURL.fileURLWithPath(@fileName.stringValue))
        @startStopButton.setTitle("Stop recording")
        @recording = true
      end
    end
  end
  
  def selectOutputFile(sender)
    panel = NSSavePanel.savePanel()
    panel.setAllowedFileTypes(["mov"])
    if panel.runModal == NSFileHandlingPanelOKButton
      @fileName.setStringValue(panel.filename)
    end
  end  
end

En ce qui concerne la modification de l'interface, je pense qu'elle est suffisamment triviale pour ne pas nécessiter plus d'explications.

Pour ceux qui le souhaitent, vous pouvez télécharger les sources de cet exemple.

Merci !

Je voudrais remercier les différentes personnes qui ont témoigné de l'intérêt pour cette série d'articles sur MacRuby. Pour répondre à quelques questions, "Oui, j'ai l'intention de continuer !", "Oui, nous allons aborder des choses un peu plus complexes à l'avenir, mais en y allant crescendo !" et "Oui, je répondrai avec plaisir à vos questions, dans la limite du temps qu'il me reste ;)". Je tiens d'ailleurs à vous rappeler l'existence de l'association RubyFrance, de sa liste de discussion et de ses Apéros1 ou vous pourrez rencontrer et discuter avec de véritables Ruby Ninjas !

1 j'ai promis d'être au prochain !

Copyright © 2009 - 2011 Grégoire Lejeune.
All documents licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License, except ones with specified licence.
Powered by Jekyll.