vini123

博客园 首页 新随笔 联系 订阅 管理

NetStream.publish捕捉摄像头的图像,并编码后发送到FMS服务器。flash 11终于支持发布h264的流。因为推送h264的流,需要flash player能编码h264格式视频,在flash player 11加入了h264编码器。

官方参考:

http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/H264VideoStreamSettings.html

编写推送h164的as程序必须要较高版本的sdk,之前使用的flex sdk 4.1的flash player版本是10.0,不能用来编写这个程序。

下载flex sdk:

http://www.adobe.com/devnet/flex/flex-sdk-download.html

 

 

h264和h263对比图(同样的码率和分辨率):

 

package
{
    import fl.controls.Button;
    import fl.controls.CheckBox;
    import fl.controls.ComboBox;
    import fl.controls.Label;
    import fl.controls.TextInput;
    
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.events.NetStatusEvent;
    import flash.media.Camera;
    import flash.media.H264Profile;
    import flash.media.H264VideoStreamSettings;
    import flash.media.Microphone;
    import flash.media.Video;
    import flash.net.NetConnection;
    import flash.net.NetStream;
    
    public class H264Publisher extends Sprite
    {    
        public function H264Publisher()
        {
            if(this.stage == null){
                this.addEventListener(Event.ADDED_TO_STAGE, this.onAddedToStage);
            }
            else{
                this.onAddedToStage(null);
            }
        }
        
        private function onAddedToStage(evt:Event):void{
            this.stage.align = StageAlign.TOP_LEFT;
            this.stage.scaleMode = StageScaleMode.NO_SCALE;
            
            var urlPanel:Sprite = new Sprite();
            this.addUrlPanel(urlPanel, onMouseClickStartPublish, onMouseClickStopPublish);
            
            var cameraPanel:Sprite = new Sprite();
            this.addCameraPanel(cameraPanel);
            
            var encodingPanel:Sprite = new Sprite();
            this.addEncodingPanel(encodingPanel);
            
            urlPanel.x = 10;
            urlPanel.y = 10;
            
            cameraPanel.x = urlPanel.x;
            cameraPanel.y = urlPanel.y + 30;
            
            encodingPanel.x = cameraPanel.x;
            encodingPanel.y = cameraPanel.y + 30;
            
            video = new Video();
            video.x = encodingPanel.x;
            video.y = encodingPanel.y + 30;
            
            this.addChild(urlPanel);
            this.addChild(cameraPanel);
            this.addChild(encodingPanel);
            this.addChild(video);
        }
        
        private var fmsUrl:String;
        private var fmsStream:String;
        private function discoveryFmsUrl():void{
            var url:String = txtUrl.text;
            
            if(url.toLowerCase().indexOf("rtmp://") < 0){
                trace("[error] the url must start with rtmp://", "error");
                return;
            }
            
            // remove the start rtmp://
            url = url.substr(url.toLowerCase().indexOf("rtmp://") + "rtmp://".length);
            
            var server:String = url.substr(0, url.indexOf("/"));
            url = url.substr(url.indexOf("/") + 1);
            
            var port:String = "1935";
            if(server.indexOf(":") >= 0){
                port = server.substr(server.indexOf(":")+1);
                server = server.substr(0, server.indexOf(":"));
            }
            
            var appIndex:int = -1;
            for(var i:int = 0; i < this.cbAppLevel.selectedIndex + 1; i++){
                if(url.indexOf("/", appIndex + 1) < 0){
                    break;
                }
                
                appIndex = url.indexOf("/", appIndex + 1);
            }
            var app:String = url.substr(0, appIndex);
            var stream:String = url.substr(appIndex + 1);
            
            // if user input ip address, set the server; otherwise, set the vhost.
            var serverIsIPAddress:Boolean = true;
            var serverItems:Array = server.split(".");
            for(i = 0; i < serverItems.length; i++){
                if(isNaN(Number(serverItems[i]))){
                    serverIsIPAddress = false;
                }
            }
            
            fmsUrl = "rtmp://" + server + ":" + port + "/" + app;
            fmsStream = stream;
        }
        
        private function buildEncodingParameters(publishStream:NetStream, c:Camera, m:Microphone):void{
            var x264profile:String = (this.cbX264Profile.selectedLabel == "Main") ? H264Profile.MAIN : H264Profile.BASELINE;
            var x264level:String = this.cbX264Level.selectedLabel;
            var x264KeyFrameInterval:int = int(this.cbX264KeyFrameInterval.selectedIndex + 1);
            var cameraWidth:int = int(this.cbCameraSize.selectedLabel.substr(0, this.cbCameraSize.selectedLabel.indexOf("x")));
            var cameraHeight:int = int(this.cbCameraSize.selectedLabel.substr(this.cbCameraSize.selectedLabel.indexOf("x") + 1));;
            var cameraFps:Number = Number(this.cbCameraFps.selectedLabel);
            var cameraBitrate:int = int(this.cbCameraBitrate.selectedLabel);
            var cameraQuality:int = 85;
            var microEncodeQuality:int = 8;
            var microRate:int = 22; // 22 === 22050 Hz
            
            trace("[Publish] h.264(x264) encoding parameters: " 
                + "profile=" + x264profile 
                + ", level=" + x264level
                + ", keyFrameInterval(gop)=" + x264KeyFrameInterval
                + "; video(camera) width=" + cameraWidth
                + ", height=" + cameraHeight
                + ", fps=" + cameraFps
                + ", bitrate=" + cameraBitrate
                + ", quality=" + cameraQuality
                + "; audio(microphone) encodeQuality=" + microEncodeQuality
                + ", rate=" + microRate + "(22050Hz)"
            );
            
            var h264Settings:H264VideoStreamSettings = new H264VideoStreamSettings();
            // we MUST set its values first, then set the NetStream.videoStreamSettings, or it will keep the origin values.
            h264Settings.setProfileLevel(x264profile, x264level); 
            publishStream.videoStreamSettings = h264Settings;
            // the setKeyFrameInterval/setMode/setQuality use the camera settings.
            // http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/VideoStreamSettings.html
            // Note This feature will be supported in future releases of Flash Player and AIR, for now, Camera parameters are used.
            /*h264Settings.setKeyFrameInterval(4);
            h264Settings.setMode(800, 600, 15);
            h264Settings.setQuality(500, 0);*/
            
            // set the camera and microphone.
            
            // setKeyFrameInterval(keyFrameInterval:int):void
            //     keyFrameInterval:int — A value that specifies which video frames are transmitted in full (as keyframes) instead of being 
            //        interpolated by the video compression algorithm. A value of 1 means that every frame is a keyframe, a value of 3 means 
            //        that every third frame is a keyframe, and so on. Acceptable values are 1 through 48.
            c.setKeyFrameInterval(x264KeyFrameInterval);
            
            // setMode(width:int, height:int, fps:Number, favorArea:Boolean = true):void
            //  width:int — The requested capture width, in pixels. The default value is 160.
            //  height:int — The requested capture height, in pixels. The default value is 120.
            //  fps:Number — The requested rate at which the camera should capture data, in frames per second. The default value is 15.
            c.setMode(cameraWidth, cameraHeight, cameraFps);
            
            // setQuality(bandwidth:int, quality:int):void
            //  bandwidth:int — Specifies the maximum amount of bandwidth that the current outgoing video feed can use, in bytes per second. 
            //        To specify that the video can use as much bandwidth as needed to maintain the value of quality, pass 0 for bandwidth. 
            //        The default value is 16384.
            //  quality:int — An integer that specifies the required level of picture quality, as determined by the amount of compression 
            //         being applied to each video frame. Acceptable values range from 1 (lowest quality, maximum compression) to 100 
            //        (highest quality, no compression). To specify that picture quality can vary as needed to avoid exceeding bandwidth, 
            //        pass 0 for quality.
            //  winlin:
            //        bandwidth is in bps not kbps. 500*1000 = 500kbps.
            //        quality=1 is lowest quality, 100 is highest quality.
            c.setQuality(cameraBitrate * 1000, cameraQuality);
            
            // if no microphone, donot set the params.
            if(m == null){
                return;
            }
            
            // The encoded speech quality when using the Speex codec. Possible values are from 0 to 10. The default value is 6. Higher numbers 
            // represent higher quality but require more bandwidth, as shown in the following table. The bit rate values that are listed represent 
            // net bit rates and do not include packetization overhead.
            m.encodeQuality = microEncodeQuality;
            
            // The rate at which the microphone is capturing sound, in kHz. Acceptable values are 5, 8, 11, 22, and 44. The default value is 8 kHz 
            // if your sound capture device supports this value. Otherwise, the default value is the next available capture level above 8 kHz that 
            // your sound capture device supports, usually 11 kHz.
            m.rate = microRate;
        }
        
        private var publishStream:NetStream;
        private var publishConnection:NetConnection;
        private function onMouseClickStartPublish(evt:MouseEvent):void{
            // if published, donothing
            if(publishStream != null){
                return;
            }
            
            this.btnStartPublish.enabled = false;
            this.btnStopPublish.enabled = true;
            
            this.discoveryFmsUrl();
            
            publishConnection = new NetConnection();
            var conn:NetConnection = publishConnection;
            
            conn.client = {};
            conn.client.onBWDone = function():void{};
            
            conn.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void{
                trace("[Publish][connection] code:" + evt.info.code);
                
                switch(evt.info.code){
                    case "NetConnection.Connect.Success":
                        publishStream = new NetStream(conn);
                        // microphone and camera
                        var m:Microphone = Microphone.getMicrophone(cbMicrophone.selectedIndex);
                        // Remark: the name is the index!
                        var c:Camera = Camera.getCamera(String(cbCamera.selectedIndex));
                        if(c == null){
                            trace("[Publish][error] failed to open camera(name=" + String(cbCamera.selectedIndex) + "): " + cbCamera.selectedLabel, "error");
                            cleanupPublishedStream();
                            break;
                        }
                        else if(c.muted){
                            trace("[Publish][error] open camera(name=" + String(cbCamera.selectedIndex) + ") failed, it's muted: " + cbCamera.selectedLabel, "error");
                            cleanupPublishedStream();
                            break;
                        }
                        
                        buildEncodingParameters(publishStream, c, m);
                        
                        publishStream.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void{
                            trace("[Publish][NetStreamStatus]" + evt.info.code);
                            
                            switch(evt.info.code){
                                case "NetStream.Publish.Start":
                                    var h264:H264VideoStreamSettings = publishStream.videoStreamSettings as H264VideoStreamSettings;
                                    trace("[Publish] video codec: " + h264.codec 
                                        + ", profile=" + h264.profile
                                        + ", level=" + h264.level
                                        + ", quality=" + h264.quality
                                        + ", fps=" + h264.fps
                                        + ", gop=" + h264.keyFrameInterval
                                        + ", bandwidth=" + h264.bandwidth
                                        + ", size=" + h264.width + "x" + h264.height);
                                    break;
                                case "NetStream.Publish.BadName":
                                    cleanupPublishedStream();
                                    break;
                            }
                        });
                        publishStream.publish(fmsStream);
                        
                        // attach video and audio.
                        trace("[Publish][debug] start publish, using camera(name=" + String(cbCamera.selectedIndex) + "): " + c.name);
                        publishStream.attachCamera(c);
                        if(m != null && !m.muted){
                            trace("[Publish][debug] start publish, using microphone(name=" + String(cbMicrophone.selectedIndex) + "): " + m.name);
                            publishStream.attachAudio(m);
                        }
                        restartPlayback();
                        break;
                    case "NetConnection.Connect.Rejected":
                    case "NetConnection.Connect.Failed":
                        cleanupPublishedStream();
                        break;
                }
            });
            
            conn.connect(fmsUrl);
        }
        private function cleanupPublishedStream():void{
            this.btnStartPublish.enabled = true;
            this.btnStopPublish.enabled = false;
            if(this.publishStream != null){
                this.publishStream.close();
            }
            if(this.publishConnection != null){
                this.publishConnection.close();
            }
            this.publishStream = null;
        }
        
        public var stream:NetStream;
        private var conn:NetConnection;
        private var video:Video;
        private function restartPlayback():void{
            // stream is playing, resume it.
            if(this.stream != null){
                this.stream.close();
            }
            
            conn = new NetConnection();
            
            conn.client = {};
            conn.client.onBWDone = function():void{};
            conn.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void{
                trace("[connection] code:" + evt.info.code + " desc:" + evt.info.description);
                
                switch(evt.info.code){
                    case "NetConnection.Connect.Success":
                        stream = new NetStream(conn);
                        video.attachNetStream(stream);
                        
                        //stream.bufferTime = 3;
                        stream.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void{
                            trace("[NetStreamStatus]" + evt.info.code + " desc:" + evt.info.description);
                        });
                        stream.client = {};
                        stream.client.onMetaData = function onMetadata(metadata:Object):void{
                            var o:Object = {};
                            for(var key:String in metadata){
                                o[key] = metadata[key];
                                trace("[metadata] " + "key=" + key + ", value=" + o[key]);
                            }
                            
                            if(metadata.width == undefined){
                                metadata.width = 10;
                                trace("[warning] metadata.width is undefied, set to 10");
                            }
                            if(metadata.height == undefined){
                                metadata.height = 10;
                                trace("[warning] metadata.height is undefied, set to 10");
                            }
                            
                            video.width = metadata.width;
                            video.height = metadata.height;
                        };
                        
                        if(!cbIsLive.selected){
                            stream.play(fmsStream, 0);
                        }
                        else{
                            stream.play(fmsStream);
                        }
                        break;
                    case "NetConnection.Connect.Rejected":
                    case "NetConnection.Connect.Failed":
                        stream.close();
                        stream = null;
                        break;
                }
            });
            conn.connect(fmsUrl);
        }
        
        private function onMouseClickStopPublish(evt:MouseEvent):void{
            this.cleanupPublishedStream();
        }
        
        private var txtUrl:TextInput;
        private var btnStartPublish:Button;
        private var btnStopPublish:Button;
        private var cbAppLevel:ComboBox;
        private var cbIsLive:CheckBox;
        private function addUrlPanel(
            panel:Sprite, 
            onMouseClickStartPublish:Function, onMouseClickStopPublish:Function
        ):void{
            var lblUrl:Label = new Label();
            lblUrl.text = "RTMP Url:";
            lblUrl.width = 50;
            panel.addChild(lblUrl);
            
            txtUrl = new TextInput();
            txtUrl.width = 380;
            txtUrl.x = lblUrl.x + lblUrl.width + 3;
            panel.addChild(txtUrl);
            
            cbIsLive = new CheckBox();
            cbIsLive.selected = true;
            cbIsLive.label = "Live";
            cbIsLive.width = 53;
            cbIsLive.x = txtUrl.x + txtUrl.width + 0;
            panel.addChild(cbIsLive);
            
            cbAppLevel = new ComboBox();
            cbAppLevel.addItem({label: "1级App"});
            cbAppLevel.addItem({label: "2级App"});
            cbAppLevel.addItem({label: "3级App"});
            cbAppLevel.addItem({label: "4级App"});
            cbAppLevel.width = 70;
            cbAppLevel.x = cbIsLive.x + cbIsLive.width + 0;
            panel.addChild(cbAppLevel);
            
            btnStartPublish = new Button();
            btnStartPublish.label = "发布流";
            btnStartPublish.width = 60;
            btnStartPublish.x = cbAppLevel.x + cbAppLevel.width + 3;
            btnStartPublish.addEventListener(MouseEvent.CLICK, onMouseClickStartPublish);
            panel.addChild(btnStartPublish);
            
            btnStopPublish = new Button();
            btnStopPublish.label = "停止发布";
            btnStopPublish.width = 60;
            btnStopPublish.enabled = false;
            btnStopPublish.x = btnStartPublish.x + btnStartPublish.width + 3;
            btnStopPublish.addEventListener(MouseEvent.CLICK, onMouseClickStopPublish);
            panel.addChild(btnStopPublish);
        }
        
        private var cbX264Profile:ComboBox;
        private var cbX264Level:ComboBox;
        private var cbX264KeyFrameInterval:ComboBox;
        private var cbCameraSize:ComboBox;
        private var cbCameraFps:ComboBox;
        private var cbCameraBitrate:ComboBox;
        private function addEncodingPanel(
            panel:Sprite
        ):void{
            var lblX264Profile:Label = new Label();
            lblX264Profile.text = "Profile:";
            lblX264Profile.width = 38;
            lblX264Profile.y = 2;
            panel.addChild(lblX264Profile);
            
            cbX264Profile = new ComboBox();
            cbX264Profile.width = 72;
            cbX264Profile.x = lblX264Profile.x + lblX264Profile.width + 0;
            panel.addChild(cbX264Profile);
            cbX264Profile.addItem({label:"Baseline"});
            cbX264Profile.addItem({label:"Main"});
            
            var lblX264Level:Label = new Label();
            lblX264Level.text = "Level:";
            lblX264Level.width = 32;
            lblX264Level.y = 2;
            lblX264Level.x = cbX264Profile.x + cbX264Profile.width + 1;
            panel.addChild(lblX264Level);
            
            cbX264Level = new ComboBox();
            cbX264Level.width = 45;
            cbX264Level.x = lblX264Level.x + lblX264Level.width + 1;
            panel.addChild(cbX264Level);
            var x264Levels:Array = ["1", "1b", "1.1", "1.2", "1.3", "2", "2.1", "2.2", "3", "3.1", "3.2", "4", "4.1", "4.2", "5", "5.1"];
            for(var i:int = 0; i < x264Levels.length; i++){
                cbX264Level.addItem({label:x264Levels[i]});
            }
            cbX264Level.selectedIndex = 8;
            
            var lblX264KeyFrameInterval:Label = new Label();
            lblX264KeyFrameInterval.text = "GOP:";
            lblX264KeyFrameInterval.width = 29;
            lblX264KeyFrameInterval.y = 2;
            lblX264KeyFrameInterval.x = cbX264Level.x + cbX264Level.width + 1;
            panel.addChild(lblX264KeyFrameInterval);
            
            cbX264KeyFrameInterval = new ComboBox();
            cbX264KeyFrameInterval.width = 87;
            cbX264KeyFrameInterval.x = lblX264KeyFrameInterval.x + lblX264KeyFrameInterval.width + 1;
            panel.addChild(cbX264KeyFrameInterval);
            for(i = 0; i < 48; i++){
                cbX264KeyFrameInterval.addItem({label:String(i + 1) + " seconds"});
            }
            cbX264KeyFrameInterval.selectedIndex = 3;
            
            var lblCameraSize:Label = new Label();
            lblCameraSize.text = "Size:";
            lblCameraSize.width = 30;
            lblCameraSize.y = 2;
            lblCameraSize.x = cbX264KeyFrameInterval.x + cbX264KeyFrameInterval.width + 1;
            panel.addChild(lblCameraSize);
            
            cbCameraSize = new ComboBox();
            cbCameraSize.width = 82;
            cbCameraSize.x = lblCameraSize.x + lblCameraSize.width + 1;
            panel.addChild(cbCameraSize);
            var sizes:Array = ["176x144", "320x240", "352x240", "352x288", "640x480", "720x480", "720x576", "800x600", "1024x768", "1280x720", "1360x768", "1920x1080"];
            for(i = 0; i < sizes.length; i++){
                cbCameraSize.addItem({label:sizes[i]});
            }
            cbCameraSize.selectedIndex = 1;
            
            var lblCameraFps:Label = new Label();
            lblCameraFps.text = "FPS:";
            lblCameraFps.width = 28;
            lblCameraFps.y = 2;
            lblCameraFps.x = cbCameraSize.x + cbCameraSize.width + 1;
            panel.addChild(lblCameraFps);
            
            cbCameraFps = new ComboBox();
            cbCameraFps.width = 58;
            cbCameraFps.x = lblCameraFps.x + lblCameraFps.width + 1;
            panel.addChild(cbCameraFps);
            var fpses:Array = ["1.00", "4.00", "5.00", "6.00", "8.00", "10.00", "12.00", "14.98", "15.00", "20.00", "24.00", "25.00", "29.97", "30.00", "59.94", "60.00"];
            for(i = 0; i < fpses.length; i++){
                cbCameraFps.addItem({label:fpses[i]});
            }
            cbCameraFps.selectedIndex = 8;
            
            var lblCameraBitrate:Label = new Label();
            lblCameraBitrate.text = "Bitrate:";
            lblCameraBitrate.width = 40;
            lblCameraBitrate.y = 2;
            lblCameraBitrate.x = cbCameraFps.x + cbCameraFps.width + 1;
            panel.addChild(lblCameraBitrate);
            
            cbCameraBitrate = new ComboBox();
            cbCameraBitrate.width = 58;
            cbCameraBitrate.x = lblCameraBitrate.x + lblCameraBitrate.width + 1;
            panel.addChild(cbCameraBitrate);
            var bitrates:Array = ["10", "50", "100", "200", "350", "500", "650", "800", "950", "1000", "1200", "1500", "1800", "2000", "2500", "20000"];
            for(i = 0; i < bitrates.length; i++){
                cbCameraBitrate.addItem({label:bitrates[i]});
            }
            cbCameraBitrate.selectedIndex = 3;
        }
        
        private var cbCamera:ComboBox;
        private var cbMicrophone:ComboBox;
        private function addCameraPanel(
            panel:Sprite
        ):void{
            // camera
            var lblCamera:Label = new Label();
            lblCamera.text = "Available Cameras:";
            lblCamera.width = 100;
            panel.addChild(lblCamera);
            
            cbCamera = new ComboBox();
            cbCamera.width = 160;
            cbCamera.x = lblCamera.x + lblCamera.width + 3;
            panel.addChild(cbCamera);
            
            var cameras:Array = Camera.names;
            for(var i:int = 0; i < cameras.length; i++){
                cbCamera.addItem({label:cameras[i]});
            }
            
            // microphone
            var lblMicrophone:Label = new Label();
            lblMicrophone.text = "Available Microphones:";
            lblMicrophone.width = 120;
            lblMicrophone.x = cbCamera.x + cbCamera.width + 10;
            panel.addChild(lblMicrophone);
            
            cbMicrophone = new ComboBox();
            cbMicrophone.width = 180;
            cbMicrophone.x = lblMicrophone.x + lblMicrophone.width + 3;
            panel.addChild(cbMicrophone);
            
            var microphones:Array = Microphone.names;
            for(i = 0; i < microphones.length; i++){
                cbMicrophone.addItem({label:microphones[i]});
            }
        }
    }
}

其中用到了FlashCS5的控件,可以将c:\Program Files (x86)\Adobe\Adobe Flash CS5\Common\Configuration\Components\User Interface.fla打开后导出swc,然后在actionscript3项目中引用这个swc就可以了。

 

文章来自:http://blog.csdn.net/winlinvip/article/details/7642942#

 

posted on 2012-10-31 18:00  黑夜丶残枫  阅读(423)  评论(0)    收藏  举报