1
BlackBerry 视频录制编程
作者: 俞伟
目录
BlackBerry视频录制编程概述 ........................................................................................... 2
获取/选定视频格式/编码 ................................................................................................... 2
录制、存储视频................................................................................................................... 5
回放已录制的视频 ............................................................................................................... 8
完整代码 ............................................................................................................................ 11
类 VideoRecordingScreen.java ................................................................................... 11
类 VideoRecordingSetupScreen.java ........................................................................ 18
类 VideoPlaybackScreen.java .................................................................................... 20
类 VideoRecordingDemo.java .................................................................................... 24
2
BlackBerry 视频录制编程概述
BlackBerry 5.0 平台以及之前的版本对多媒体的支持依赖于 JSR 135,也就是
J2ME 平台
的 MMAPI。BlackBerry 手机上录制视频也依靠 MMAPI,支持 3gpp 视频格式,支持的视频编
码包括 MPEG-4, H263, H264, 音频编码包括 AAC, PCM, AMR。具体的视频音频编码支持依黑
莓机型而定。
BlackBerry 视屏录制编程包括以下几部分:
1. 获取/选定手机对视频格式/编码的支持
2. 录制/存储视频
3. 回放已录制的视频
获取/选定视频格式/编码
通过系统提供的 API 可以获得该机型视频支持的所有视频规格:
encodingsString 包含了全部能够支持的视频规格,但它是一个 String,还不能够直接使用,
需要进一步解析。以下代码解析视频规格,并返回规格列表:
/**
* 获取黑莓手机所支持的视屏规格列表
*/
public static String[] getVideoEncodings()
{
// 获取黑莓手机设备支持的视频格式/编码
String encodingsString = System.getProperty("video.encodings");
// 如果没有支持的视频编码,返回空
if( encodingsString == null )
{
return null;
}
// 解析出视频规格
Vector encodings = new Vector();
int start = 0;
int space = encodingsString.indexOf(' ');
String encodingsString = System.getProperty("video.encodings");
3
解析后的视频规格范例如下所示:
“encoding”字段表示视频格式
“mode”表示播放模式,模式会自动设置屏幕尺寸和编码
“width”表示屏幕宽度
“height”表示屏幕高度
“video_codec”表示视频编码
“audio_codec”表示音频编码
视频规格如图所示:
while( space != -1 )
{
encodings.addElement(encodingsString.substring(start, space));
start = space + 1;
space = encodingsString.indexOf(' ', start);
}
encodings.addElement(encodingsString.substring(start, encodingsString.length()));
String[] encodingArray = new String[encodings.size()];
encodings.copyInto(encodingArray);
return encodingArray;
}
“encoding=video/3gpp&mode=standard”
“encoding=video/3gpp&mode=mms”
“encoding=video/3gpp&width=480&height=360&video_codec=MPEG-4&audio_codec=AMR”
“encoding=video/3gpp&width=176&height=144&video_codec=H263&audio_codec=AMR”
“encoding=video/3gpp&width=480&height=360&video_codec=H263&audio_codec=AAC”
“encoding=video/3gpp&width=480&height=360&video_codec=H264&audio_codec=AAC”
4
5
录制、存储视频
录制视频主要用到 Player,VideoControl,RecordControl,ByteArrayOutputStream。使用 Player
打开摄像头,VideoControl 将捕捉的视频显示到屏幕上,RecordControl 控制录制过程,
ByteArrayOutputStream 作为视频录制的缓存。以下三段代码范例分别为配置/启动摄像头,
开始录制,和保存已录制的视频。
/**
* 构造一个屏幕显示和录制摄像头录制的视频
*/
public VideoRecordingScreen(String encoding, String filePath)
{
try{
// 开始捕捉视频
// encoding 是上一部分提到的视频规格
_player = javax.microedition.media.Manager.createPlayer("capture://video?" + encoding);
_player.start();
_videoControl = (VideoControl) _player.getControl("VideoControl");
_recordControl = (RecordControl) _player.getControl("RecordControl");
// 将捕捉的视频显示在 Field 控件上
Field videoField = (Field)
_videoControl.initDisplayMode(VideoControl.USE_GUI_PRIMITIVE,
"net.rim.device.api.ui.Field");
try{
// 视频尺寸为全屏
_videoControl.setDisplaySize( Display.getWidth(),
Display.getHeight() );
}
catch( MediaException me )
{}
add(videoField);
// 视频缓存,捕捉到的视频将写入缓存
_outStream = new ByteArrayOutputStream();
_videoFile = filePath;
startRecord();
}
6
以上代码配置并启动摄像头。
catch( Exception e )
{
// Dispose of the player if it was created
if( _player != null )
{
_player.close();
}
_player = null;
deleteAll();
removeAllMenuItems();
VideoRecordingDemo.errorDialog(e.toString());
}
}
/**
* 开始录制视频
*/
private void startRecord()
{
try
{
if( !_pendingCommit )
{
// 重置缓存
_outStream.reset();
// 将缓存作为录制缓存
_recordControl.setRecordStream(_outStream);
_pendingCommit = true;
_committed = false;
}
// 开始录制
_recordControl.startRecord();
_recording = true;
}
7
以上代码开始录制视频。
catch( Exception e)
{
VideoRecordingDemo.errorDialog(e.toString());
}
}
/**
* 保存录像
*/
private void commitRecording()
{
try
{
// 这是最关键的一步,保存录制的视频
_recordControl.commit();
// 重置参数
_pendingCommit = false;
_committed = true;
_recording = false;
// 获取视频数据流
byte[] data = _outStream.toByteArray();
// 以文件的形式保存视频
FileConnection fconn = (FileConnection) Connector.open(_videoFile);
if (fconn.exists()){
fconn.delete();
}
fconn.create();
OutputStream os = fconn.openOutputStream();
os.write(data);
os.flush();
os.close();
fconn.close();
8
以上代码保存/存储视频。
回放已录制的视频
前一步录制的视频以文件的形式保存在手机目录中,回放视频需要文件路径, 载入文件,
使用 Player 在 Field 上进行播放。代码如下:
Dialog.alert("Committed");
}
catch( Exception e )
{
VideoRecordingDemo.errorDialog("RecordControl#commit() threw " + e.toString());
}
}
/**
* 构造视频回放屏幕,按指定路径装载视频文件
*/
Public VideoPlaybackScreen(String file)
{
boolean notEmpty;
// 检测给定路径是否有媒体文件
try
{
FileConnection fconn = (FileConnection) Connector.open(file);
notEmpty = fconn.exists() && fconn.fileSize() > 0;
fconn.close();
}
catch( IOException e )
{
Dialog.alert("Exception while accessing the video filesize:\n\n" + e);
notEmpty = false;
}
9
使用给定的路径创建 _videoPlayer。
// 如有媒体文件并有内容,初始化界面进行播放
if( notEmpty )
{
try
{
// 创建 Video Player
_videoPlayer = javax.microedition.media.Manager.createPlayer(file);
initScreen();
}
catch( Exception e )
{
Dialog.alert("Exception while initializing the playback video player\n\n" + e);
}
}
else
{
add(new LabelField("The video file you are trying to play is empty"));
}
}
private void initScreen() throws Exception{
_videoPlayer.realize();
// 注册媒体播放时间响应
_videoPlayer.addPlayerListener(new PlayerListener(){
public void playerUpdate(Player player, String event, Object eventData){
// 定义媒体文件播放完毕时的响应
if( event == PlayerListener.END_OF_MEDIA ){
UiApplication.getUiApplication().invokeLater(new Runnable(){
public void run(){
Dialog.alert("Finished playing");
close();
}
});
}
}
});
10
初始化屏幕开始回放视频。_videoPlayer.realize() 获取视频文件相关信息,为播放做好准备。
之后_videoPlayer 注册了媒体播放事件侦听,当媒体文件播放完毕时有事件响应。从
_videoPlayer 中获取 VideoControl,VideoControl 再返回视屏播放所占据的 Field,添加这个
Field 到屏幕上来显示视屏。VolumeControl 可以定义声音大小,也可以从_videoPlayer 中获取。
// 获取 VideoControl 来获得播放视频的 Field
_videoControl = (VideoControl)_videoPlayer.getControl("VideoControl");
Field vField = (Field) _videoControl.initDisplayMode(VideoControl.USE_GUI_PRIMITIVE,
"net.rim.device.api.ui.Field");
add(vField);
// 获取 VolumeControl 来指定声音大小
VolumeControl vol = (VolumeControl) _videoPlayer.getControl("VolumeControl");
vol.setLevel(30);
}
11
完整代码
类 VideoRecordingScreen.java
package com.rim.samples.device.videorecordingdemo;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.system.Display;
import javax.microedition.io.Connector;
import javax.microedition.io.file.FileConnection;
import javax.microedition.media.*;
import javax.microedition.media.control.*;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.OutputStream;
/**
* This screen allows the user to record videos to a file or to a stream and
* enables the user to open the VideoPlaybackScreen to play the recorded video.
*/
public class VideoRecordingScreen extends MainScreen
{
private boolean _pendingCommit;
private boolean _committed;
private boolean _recording;
private String _videoFile;
private Player _player;
private VideoControl _videoControl;
private RecordControl _recordControl;
private boolean _displayVisible;
private ByteArrayOutputStream _outStream;
public VideoRecordingScreen(String encoding, String filePath)
{
12
if (encoding == null)
{
throw new NullPointerException("Video encoding can not be null");
}
if (filePath == null)
{
throw new NullPointerException("File path can not be null");
}
try
{
// 开始捕捉视频
// encoding 是视频规格
_player = javax.microedition.media.Manager.createPlayer("capture://video?" + encoding);
_player.start();
_videoControl = (VideoControl) _player.getControl("VideoControl");
_recordControl = (RecordControl) _player.getControl("RecordControl");
// 将捕捉的视频显示在 Field 控件上
Field videoField = (Field) _videoControl.initDisplayMode(VideoControl.USE_GUI_PRIMITIVE,
"net.rim.device.api.ui.Field");
try{
// 视频尺寸为全屏
_videoControl.setDisplaySize( Display.getWidth(), Display.getHeight() );
}
catch( MediaException me ){
// setDisplaySize is not supported
}
add(videoField);
// 视频缓存,捕捉到的视频将写入缓存
_outStream = new ByteArrayOutputStream();
_videoFile = filePath;
startRecord();
}
13
catch( Exception e )
{
// Dispose of the player if it was created
if( _player != null )
{
_player.close();
}
_player = null;
deleteAll();
removeAllMenuItems();
VideoRecordingDemo.errorDialog(e.toString());
}
}
/**
* Commits the current recording
*/
private MenuItem _commit = new MenuItem("Commit recording", 0, 0){
public void run(){
commitRecording();
}
};
/**
* Plays the recording
*/
private MenuItem _playRecording = new MenuItem("Play recording", 0, 0){
public void run(){
// Create the playback screen from the chosen video source
VideoPlaybackScreen playbackScreen = new VideoPlaybackScreen(_videoFile);
// Hide the video feed since we cannot display video from the camera
// and video from a file at the same time.
_videoControl.setVisible(false);
_displayVisible = false;
UiApplication.getUiApplication().pushScreen(playbackScreen);
}
};
14
/**
* Resets the recording
*/
private MenuItem _reset = new MenuItem("Reset recording", 0, 0){
public void run(){
try{
_recordControl.reset();
}
catch( Exception e ){
VideoRecordingDemo.errorDialog("RecordControl#reset threw " + e.toString());
}
}
};
/**
* Shows the video display
*/
private MenuItem _showDisplay = new MenuItem("Show display", 0, 0){
public void run(){
_videoControl.setVisible(true);
_displayVisible = true;
}
};
private MenuItem _hideDisplay = new MenuItem("Hide display", 0, 0){
/**
* @see java.lang.Runnable#run()
*/
public void run(){
_videoControl.setVisible(false);
_displayVisible = false;
}
};
private MenuItem _startRecord = new MenuItem("Start recording", 0, 0){
public void run()
{
startRecord();
}
};
15
private MenuItem _stopRecord = new MenuItem("Stop recording", 0, 0){
public void run(){
stopRecord();
}
};
public boolean onClose(){
// Stop capturing video from the camera
if( _player != null ){
_player.close();
}
return super.onClose();
}
protected boolean onSavePrompt(){
// Suppress the save prompt
return true;
}
protected void makeMenu(Menu menu, int instance){
super.makeMenu(menu, instance);
if(_recording){
menu.add(_stopRecord);
}
else{
menu.add(_startRecord);
}
if( _pendingCommit ){
menu.add(_commit);
menu.add(_reset);
}
if(_committed){
menu.add(_playRecording);
}
if( _displayVisible){
menu.add(_hideDisplay);
}
else{
menu.add(_showDisplay);
}
}
16
protected boolean invokeAction(int action){
switch(action){
case ACTION_INVOKE: // Trackball click
if(_recording){
int response = Dialog.ask(Dialog.D_YES_NO, "Recording paused. Commit
recording?", Dialog.YES);
if(response == Dialog.YES){
this.commitRecording();
}
}
return true; // We've consumed the event
}
return super.invokeAction(action);
}
protected void onVisibilityChange(boolean visible){
if( visible && _player != null ){
_videoControl.setVisible(true);
_displayVisible = true;
}
}
private void startRecord(){
try{
if( !_pendingCommit ){
_outStream.reset();
// 将缓存作为录制缓存
_recordControl.setRecordStream(_outStream);
_pendingCommit = true;
_committed = false;
}
// 开始录制
_recordControl.startRecord();
_recording = true;
}
catch( Exception e ){
VideoRecordingDemo.errorDialog(e.toString());
}
}
17
private void stopRecord(){
try{
_recordControl.stopRecord();
_recording = false;
}
catch( Exception e ){
VideoRecordingDemo.errorDialog("RecordControl#stopRecord() threw " + e.toString());
}
}
private void commitRecording(){
try{
_recordControl.commit();
_pendingCommit = false;
_committed = true;
_recording = false;
// 获取视频数据流
byte[] data = _outStream.toByteArray();
// 以文件的形式保存视频
FileConnection fconn = (FileConnection) Connector.open(_videoFile);
if (fconn.exists()){
fconn.delete();
}
fconn.create();
OutputStream os = fconn.openOutputStream();
os.write(data);
os.flush();
os.close();
fconn.close();
Dialo