BitmapData.draw() and Video Capture - Security sandbox violation.
Par eKameleon le samedi, mars 10 2007, 12:00 - Flash Media Server - Lien permanent
Un petit article rapide pour vous parler d'un problème que j'ai rencontré ces derniers temps en utilisant la classe BitmapData pour capturer le contenu d'un clip contenant un objet Video qui réceptionne un flux vidéo live via la classe NetStream passant par une connexion Flash Media Server.
En gros j'avais besoin de prendre des photos d'un flux vidéo passant par FMS dans une animation contenue dans un page on-line. J'utilise donc pour réaliser cette "prouesse" tout simplement la méthode draw() de la classe BitmapData. Malheureusement à chaque fois j'obtenais une capture de mon clip avec une image toute blanche alors qu'en testant dans Flash je n'avais aucun problème pour réaliser cette capture.
En cherchant dans Google je suis rapidement tombé sur l'article de Kiroukou traitant de sa classe DistortImage 2.0 qui permet de déformer une image en ActionScript. J'ai ainsi tout de suite pu trouvé la nature de mon problème avec le commentaire éclairé de Riggs qui indique qu'il obtient dans ses tests un message d'erreur de type "violation de la sécurité sandbox".
Il m'a suffit à ce moment là de lancer une nouvelle recherche dans Google pour trouver ma solution sur le blog de Keun Lee dans un article intitulé : Flex 2 VideoDisplay + Security sandbox violation : BitmapData.draw on RTMP
Voyons donc maintenant la solution à cet épineux problème avec un petit bout de code AS2 :
import flash.display.BitmapData ; // The NetConnection instance. var nc:NetConnection = new NetConnection() ; nc.onStatus = function( oInfo:Object ) { trace( "-> " + oInfo.code ) ; } nc.connect( "rtmp://localhost/test_video" ) ; // The NetStream to publish the Camera var nsOut:NetStream = new NetStream( nc ) ; nsOut.attachVideo( Camera.get() ) ; nsOut.publish("test_live") ; // The NetStream instance to play the external Video live stream. var nsIn:NetStream = new NetStream( nc ) ; nsIn.play("test_live", -1) ; // The video object in the movieclip with the name "container" var video:Video = container.video ; video.attachVideo( nsIn ) ; // The BitmapData instance. var bmp:BitmapData = new BitmapData(container._width , container._height) ; Key.addListener(this) ; this.onKeyDown = function() { video.attachVideo( null ) ; bmp.draw(container) ; video.attachVideo( nsIn ) ; var capture:MovieClip = createEmptyMovieClip("capture", 10) ; capture._x = container._x + container._width + 10 ; capture._y = container._y ; capture.attachBitmap( bmp, 1 ) ; }
Bon le code le plus important dans tout le script ci-dessus c'est la partie contenue dans la fonction callback onKeyDown :
video.attachVideo( null ) ; bmp.draw(container) ; video.attachVideo( nsIn ) ;
Comme vous pouvez le voir, la technique est simplement d'arrêter la diffusion du flux vidéo pendant la capture et ensuite de la relancer après l'appel de la fonction draw(). La solution est donc très simple... il fallait juste y penser.
Pour ceux qui utilisent VEGAS à noter que j'ai intégré dans l'extension ASGard une classe AS2 PrintScreen qui permet de capturer facilement via la classe BitmapData le contenu d'un clip et ainsi de renvoyer un buffer de type Array contenant l'ensemble des points (pixels) de l'image capturée. Cette classe me permet ensuite d'envoyer vers un script AMFPHP l'ensemble des points de l'image et de créer et sauver une image au format jpg sur le serveur à condition d'utiliser la librairie GD PHP.
Voici un petit exemple rapide d'utilisation de cette classe :
import flash.display.BitmapData ; import asgard.display.Bitmap ; import asgard.events.PrintScreenEvent ; import asgard.media.PrintScreen ; import asgard.events.RemotingEvent ; import asgard.net.remoting.RemotingService ; import vegas.events.Delegate ; import vegas.events.EventListener ; Stage.scaleMode = "noScale" ; var captureComplete:Function = function ( e:PrintScreenEvent ):Void { trace( "> capture " + e.getType() + " in " + e.getTime() + " ms") ; snap.bitmapData = e.getBitmapData() ; buffer = e.getBuffer() ; } var captureProgress:Function = function ( e:PrintScreenEvent ):Void { trace( "> capture " + e.getType() + " : " + e.getPercent() + " %") ; } var captureStart:Function = function ( e:PrintScreenEvent ):Void { trace( "> capture " + e.getType() ) ; snap.bitmapData = null ; } var serviceFault:Function = function(e:RemotingEvent):Void { var type:String = e.getType() ; var target:RemotingService = e.getTarget() ; trace("> service " + e.getType() + " > " + e.getCode()) ; trace(" > level : " + e.getLevel()) ; trace(" > line : " + e.getLine()) ; trace(" > methodName : " + e.getMethodName()) ; trace(" > description : " + e.getDescription()) ; } var serviceFinish:Function = function (e:RemotingEvent):Void { trace("> service " + e.getType()) ; } var serviceResult:Function = function(e:RemotingEvent):Void { var type:String = e.getType() ; trace("> service " + type + " : " + e.getResult() ) ; } var serviceStart:Function = function (e:RemotingEvent):Void { trace("> service " + e.getType()) ; } var service:RemotingService = new RemotingService() ; service.addEventListener( RemotingEvent.FAULT, new Delegate(this, serviceFault) ) ; service.addEventListener( RemotingEvent.FINISH, new Delegate(this, serviceFinish) ) ; service.addEventListener( RemotingEvent.RESULT, new Delegate(this, serviceResult) ) ; service.addEventListener( RemotingEvent.START, new Delegate(this, serviceStart) ) ; // The buffer reference to save all pixels of the bitmap capture. var buffer:Array ; var cam:Camera = Camera.get() ; var video:Video = container.video ; video.attachVideo(cam) ; video.width = 320 ; video.height = 240 ; var snap_mc:MovieClip = this.createEmptyMovieClip("snap_mc", 1) ; var snap:Bitmap = new Bitmap( "snapShot", snap_mc, null, null, true ) ; snap.x = container._x + container._width + 10 ; snap.y = container._y ; var ps:PrintScreen = new PrintScreen( container ) ; ps.addEventListener( PrintScreenEvent.FINISH , new Delegate(this, captureComplete) ) ; ps.addEventListener( PrintScreenEvent.PROGRESS , new Delegate(this, captureProgress) ) ; ps.addEventListener( PrintScreenEvent.START , new Delegate(this, captureStart) ) ; var gatewayUrl = "http://localhost/test/php/gateway.php" ; // gateway of the AMFPHP service var id:Number = 0 ; // the button to capture all pixels in the buffer of the PrintScreen instance. captureButton.onPress = function() { ps.run() ; } // the button to launch the AMFPHP service and save the picture saveButton.onPress = function() { service.gatewayUrl = gatewayUrl ; service.serviceName = "SnapShot" ; service.methodName = "save" ; if (buffer.length > 0) { service.params = [ id++ , ps.getBuffer(), ps.width , // width of the capture. ps.height // height of the capture. ] ; service.trigger() ; } }
Comme vous pouvez le voir dans le code ci-dessus j'utilise :
- Un objet Video ayant pour nom d'occurence "video" contenu dans un clip "container" posé sur la scène de flash.
- 2 clips avec les noms d'occurrence : captureButton et saveButton. Ces clips servent de bouton et permettent de lancer la capture et l'enregistrement de l'image.
- Ma classe asgard.display.Bitmap (polymorphe avec la classe AS3 flash.display.Bitmap) pour afficher rapidement le résultat de la capture.
- Ma classe asgard.net.remoting.RemotingService pour communiquer avec AMFPhp.
J'espère pouvoir bientôt trouver le temps pour écrire des tutoriels plus détaillés sur les différentes classes de cet exemple.
Voici mon service AMFPHP qui me permet de tester l'enregistrement de l'image sur le serveur. A noter que j'utilise pour mes tests le serveur WAMP en activant l'extension GD en passant par les options du serveur : PHP Settings -> PHP Extensions -> php_gd2
geshi php
<?php
/**
* This remoting AMFPhp service is used to save a picture in the server.
* @author eKameleon
*/
class SnapShot
{
/**
* Creates a new SnapShot instance.
*/
function SnapShot ()
{
$this->methodTable = array
(
"save" => array
(
"description" => "Create and save a new SnapShot on the db" ,
"access" => "remote"
)
) ;
}
/**
* Inserts a stack of all pixels to creates a picture on the server.
* @param the id of the snapshot.
* @param $stack the buffer of all pixels to create a picture.
* @param $width the width of the snapshot (default value is 340).
* @param $height the height of the snapshot (default value is 240).
* @param $quality the quality (default value is 85%).
* @return 'true' if the save is success.
*/
/*public*/ function save( $id , $stack , $width=320 , $height=240 , $quality=85 )
{
$root = "../../" ;
$path = "library/" ;
if (count($stack) > 0)
{
$file = 'picture_' . $id . '.jpg' ;
$output = imagecreatetruecolor( $width , $height ) ;
$cpt = 0 ;
for( $i = 0 ; $i < $height ; $i++ )
{
for( $j = 0 ; $j < $width ; $j++ )
{
imagesetpixel( $output, $j, $i, $stack[$cpt] ) ;
$cpt++;
}
}
imageantialias( $output, true ) ;
header("Content-type:image/jpeg");
return imagejpeg( $output, $root . $path . $file , $quality ) ;
}
return false ;
}
}
?>
Dans mon exemple j'enregistre l'image dans un répertoire /library situé à la racine de mon répertoire de test sur le serveur PHP.
La capture et l'enregistrement de l'image fonctionne bien, il est certain qu'en AS3 et le protocol AMF3 il doit être beaucoup plus simple d'enregistrer une image Bitmap, sachant qu'il est également possible d'encoder l'image au format JPG ou PNG directement en AS3 mais en AS2 avec Flash8 cette solution reste une bonne solution.
Comme d'habitude pour ceux qui découvrent VEGAS dans cet article je vous propose d'aller consulter mon tutoriel sur l'installation des sources de VEGAS via Subversion.
Commentaires
Le code en AS2 ne marche pas en crossdomain
loloviolo.com/mmbbs/MMBBS...
Appuyer sur une touche pour arreter le FLV et SnapShot pour le draw.
Si quelqu'un voit pourquoi ... Merci
Hello


Ta vidéo provient d'un download progressif sur un serveur web distant ? Si c'est le cas faut penser à mettre en place un crossdomain.xml sur ton serveur distant. Sinon si c'est autre chose faut être plus précis sur ta question
EKA+
Salut EKA, merci pour ta reponse ultra rapide.

Il s'agit effectivement d'un flv sur serveur web distant ... Mais il y a un crossdomain.xml
krux.bita.nl/crossdomain....
J'ai essaye sur un autre serveur, toujours le meme *** Security SandBox Violation ***
Je trouve ca fou ce system de securite : On peut lire un FLV externe sans crossdomain.xml mais on ne peut faire un draw du movieclip container meme la connection distante fermee.
Voici mon code (le tiens en fait):
import flash.display.BitmapData ;
filename = "krux.bita.nl/streams/stre...
// The NetConnection instance.
nc = new NetConnection();
nc.connect(null);
// The NetStream instance to play the external Video live stream.
var nsIn:NetStream = new NetStream( nc ) ;
nsIn.play(filename) ;
// The video object in the movieclip with the name "container"
var video:Video = container.video ;
video.attachVideo( nsIn ) ;
// The BitmapData instance.
var bmp:BitmapData = new BitmapData(container._width , container._height) ;
Key.addListener(this) ;
this.onKeyDown = function()
{
trace("Stopped");
video.attachVideo("") ;
nsIn.close();
nc.close();
}
btn.onRelease = function() {
bmp.draw(container) ;
trace("SnapShot");
var capture:MovieClip = createEmptyMovieClip("capture", 10) ;
capture._x = container._x + container._width + 10 ;
capture._y = container._y ;
capture.attachBitmap( bmp, 1 ) ;
};
Tu as lu mon tutoriel ? Car la réponse est au tout début et dans ton code tu n'utilises pas du tout mon truc et astuce pour fixer le problème... bref soit plus attentif à ce que je dis au dessus


Pour d'autres type de question comme celle ci je te conseil d'aller sur un forum (mediabox, jeanphiblog, ...) c'est tout de même plus adapté à ce type de question
EKA+
Merci encore pour ta reactivite.
Cependant, tu n'as pas du bien lire mon code. J'utilise exactement ton astuce "arrêter la diffusion du flux vidéo pendant la capture"
J'ai juste fais en 2 temps, d'abord detacher la video et fermer le stream puis lancer le draw.
Pour te montrer,voici un autre exemple avec ton code :
www.raprace.nl/forums/MMB...
Le code :
import flash.display.BitmapData ;
filename = "krux.bita.nl/streams/stre...
// The NetConnection instance.
var nc:NetConnection = new NetConnection() ;
nc.onStatus = function( oInfo:Object )
{
trace( "-> " + oInfo.code ) ;
}
nc.connect(null);
// The NetStream instance to play the external Video live stream.
var nsIn:NetStream = new NetStream( nc ) ;
nsIn.play(filename) ;
// The video object in the movieclip with the name "container"
var video:Video = container.video ;
video.attachVideo( nsIn ) ;
// The BitmapData instance.
var bmp:BitmapData = new BitmapData(container._width , container._height) ;
Key.addListener(this) ;
this.onKeyDown = function()
{
trace("KeyDOwn");
video.attachVideo( null ) ;
bmp.draw(container) ;
video.attachVideo( nsIn ) ;
var capture:MovieClip = createEmptyMovieClip("capture", 10) ;
capture._x = container._x + container._width + 10 ;
capture._y = container._y ;
capture.attachBitmap( bmp, 1 ) ;
}
J'insiste
J'espère que tu comprendras que l'objectif ici n'est pas le même 

Pour d'autres type de question comme celle ci je te conseil d'aller sur un forum (mediabox, jeanphiblog, FCNG ...) c'est tout de même plus adapté pour ce type de question
Mon blog n'est pas un forum de discussion
EKA+
J'ai oublié de te mettre le lien de mon Google Groups lié à mon blog :

groups.google.com/group/e...
EKA+
Bon bah c'est fait. J'espere trouver ma reponse.

J'ai mis des liens vers ce post ... je pense que tu n'es pas contre
Merci de tes reponses en tout cas. Ca en fait des commentaires d'un coup sur ce post
LoLoVioLo