flash actionscript 3.0 publish h264 stream
2012-07-25 15:20
681 查看
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对比图(同样的码率和分辨率):
as3.0代码:
[javascript]
view plaincopyprint?
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://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对比图(同样的码率和分辨率):
as3.0代码:
[javascript]
view plaincopyprint?
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]});
}
}
}
}
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就可以了。
相关文章推荐
- flash actionscript 3.0 publish h264 stream
- flash actionscript 3.0 发布H264的视频流
- 积少成多Flash ActionScript 3.0(6) - 绘图之图形和转换
- 【转载】战速决Flash ActionScript 3.0 - 以文本形式、XML形式和JSON形式与ASP.NET通信续
- 《Flash ActionScript 3.0 动画高级教程》
- 积少成多Flash ActionScript 3.0(1) - 基础之数据类型、操作符和流程控制语句
- 积少成多Flash ActionScript 3.0(4) - 实例之Hello World, 时钟, 计时器
- Flash ActionScript 3.0 - 以文本形式、XML形式和JSON形式与页面通信
- 积少成多Flash ActionScript 3.0(6) - 绘图之图形和转换
- FLASH ActionScript 3.0 sns cocial game 开发中的定时器
- [FLASH] Create a Dynamic Slideshow with ActionScript 3.0 and XML
- Flash Action Script 3.0 实现 MovieClip 的拖拽
- 积少成多Flash ActionScript 3.0(1) - 基础之数据类型、操作符和流程控制语句
- <<Flash.ActionScript.3.0动画高级教程>>
- Flash ActionScript 3.0 通过asp.net 访问 数据库
- Flash ActionScript 3.0的Socket连接的策略文件
- 一本好书 Flash ActionScript 3.0高级动画教程
- 战速决Flash ActionScript 3.0 - 以文本形式、XML形式和JSON形式与ASP.NET通信续
- 积少成多Flash ActionScript 3.0(3) - 基础之以文本形式、XML形式和JSON形式与ASP.NET通信
- flash actionscript 3.0 动画教程:函数之形式参数和实际参数