java

位置:IT落伍者 >> java >> 浏览文章

技术交流:QuickTime流媒体和Java(图)


发布日期:2019年02月08日
 
技术交流:QuickTime流媒体和Java(图)

这并不是即将问世的QuickTime for Java book一书的摘录虽然我希望是的

你看问题是在QTJ世界中大多数的我们都一直假定QTJ中的流媒体API已经损坏我并不是想为此事而掩盖什么好的我继续来通过各种各样的人通过不同的技术进行工作的掩盖获取去这样做但是我不想再来一次此外流媒体沖突的情况似乎特别的糟糕没有人能得到它的演示代码工作方式――this post to the quicktimejava list 是令许多用户对获取苹果公司的AudioBroadcaster 和DrawableBroadcaster 演示工作方式绝望的典型让事情更糟糕演示之一依靠一个在QTJ 版本中作为退回到原始的GUI提供的已经被取消的GUI预览组件类提供组件只对Movies MovieControllers 和GraphicsImporters 而不是流式的Presentations 视频捕捉或者某些图形美好得像来自多种资源合成制作所以官方给出的演示它首先看起来是不会工作和现在的关键类有沖突(如果在Java 中运行会抛出RuntimeExceptions异常)预测实际的流式内容和QTJ 看上去会非常糟糕

令我欣喜甚至是有点吃惊的是有报道称流媒体能够在QTJ 中工作在本文章中我将介绍通过QTJ实现简单的网络广播的基础

需求

QuickTime的流媒体API在Java中由只可在Mac OS(Classic和OS X)中运行的包quicktimestreaming声明在QTJ中存在Windows版本的类但是它们却不能工作但是你可以使用Windows版本的QuickTime作为流媒体的客户端如果在Java中运行并不是关键的你可获取Darwin Streaming Server一个开源项目可在Windows Server 和 Server上运行如同在Solaris 和 Red Hat Linux 上一样

使用QuickTime流媒体最简单的事情就是我在此说明的生动内容你需要至少一个音频输入设备如一个内置的麦克风或者一个耳机当然拥有一个QuickTime支持的摄像机如一个iSight将会更让人印象深刻

Streaming 是什么不是什么

给出了术语的含义并不容易明确术语Streaming 的正确含义例如QuickTime长期支持一种快速启动的特征――如果QuickTime明确拥有足够的开始播放的资源并且不会用完目前下载速率下的资源一段录像能开始播放――那是一些用户将Streaming 的一种形式弄错了自然的这有它自己的优势容易创建并且确保了所有的包都到达了客户端但是真正的 Streaming 换句话说Streaming 符合Internet工程工作小组(IETF)的标准这是一个完全不同的问题直到QuickTime 才被支持并且直到QTJ 才支持Java

Streaming 的形式允许服务器控制传输但很难在实时中保证最优化运行客户端未下载潜在的大文件这样的方法是独特的便利的直接广播事实上QuickTime的流媒体使用两种实时的流媒体传输协议实时传输协议(RTP)来传输媒体数据包实时流协议(RTSP)用于控制信息RTP使用潜在的有损UDP连接所以所有的人都有意的忍耐再传输期间的包的丢失这就意味着客户端需要友好的操作未获取所有数据的视频帧或者音频例子更好的方法是通过基于TCP/IP的连接它可以使用不确定的重试(也会因此需要一个不确定的时间)来获取丢失的包

Presentation和SDP文件

在QuickTime中流媒体传输等同一个电影――一个电影可以有音轨和视轨一个元数据的聚集将它们全都联系在一起此表示会将一些多种的音频和视频流的元数据联系起来音频和视频你非常喜欢的流媒体种类也是值得关注的自从某些其他的媒体类型(spritesFlash内容)被QuickTime支持后并没有操作好失去的包并不适合作为流媒体传播

你可能被建议去建立一个流媒体你会需要创建一个Presentation 并开始它但是现在呢?最普遍的这样做的方法是创建一个会话描述协议(SDP)文件将其放入静态工厂方法PresentationfromFile()SDP文件以一种适当的简单的文本格式由RFC 和several updates所定义我发现这些都是早期的理论而不是实际操作但是稍后让我们担心的是执行详细信息这是一个被一些Apple的流媒体使用的例子在Tim Monroe的QuickTime Toolkit Volume Two中

v=

c=IN IP //

m=audio RTP/AVP

m=video RTP/AVP

a=rtpmap: H

以下是每一行的解释

·v=这是SDP的版本号在这里版本号是表示在SDP中没有次要的号码

·c=IN IP //这是提供在描述中使用的连接的信息IN IP 表示是一个IPv的网络地址是地址(注意这是一个多点传输地址所有许多的客户端能连接到广播)是存在时间是临近使用的多点传输地址的数量

·m=audio RTP/AVP m=这一行定义了用于广播的流媒体在这里明显的是audio发送经由到RTP到端口在简单的QCELP音频中定义了有效负载类型这些在RFC 中定义了

·m=video RTP/AVP 这一媒体行定义了一个video媒体流由RTP传输到端口有效负载类型为所以使用表示在原始的RFC中视频格式没有给出负载类型在SDP中替换它会被映射到一个众所周知的常量中

·a=rtpmap: H这个完成键入在前一行指出的动态负载使用此类型你会使用一个在之间的值(本例中是然后用一个字符串命名此负载类型(H

这当然好但是当在我的例程序中使用它的时候我只获得了一个视频流却没有声音所以我使用了一个很不同的SDP最初在QTJ的DrawableBroadcaster演示中出现是的他们不赞同这么做

m=audio RTP/AVP

c=IN IP

a=rtpmap: xqt

m=video RTP/AVP

a=rtpmap: xqt

这里最大的不同就是音频和视频都使用了相同的动态负载映射这并不是针对一个真正的编码器而是一般的xqt在这里胜利的是你能在运行时间上挑选任一QuickTime的音频和视频编码器而不是在SDP文件中强迫导致底侧是这些可以不是由非QuickTime客户端可分析的反之使用十分标准的和/或者旧的编码器并且在SDP中指定他们使它更像其他的客户端(Real JMF等)能够操作你的系统

这就是你的SDP文件现在不要加入一个Presentation

创建Presentation

我们的流媒体服务器程序调用LittleBroadcast这并没有多少代码只有不过在本文中我将一步一步的进行解释一般的部分但提供其全部的清单在后面的Resources章节中有可用到一个targz文件连同SDP文件和一个Ant构建文件

package commacinvalidnameqtjstreaming;import quicktime*;

import quicktimestd*;

import quicktimeutil*;

import quicktimeqd*;

import quicktimeio*;

import quicktimestreaming*;

import quicktimeapptime*;

import javaio*;import javaawt*;

import javaawtevent*;

public class LittleBroadcast extends Tasking implements ActionListener {

这是一长串典型的引入QuickTime包括了使用其QDGraphics来提供一个摄像机画面以外的图形界面的qd读取SDP文件的io用于流媒体API的streaming以及获得给予Presentation运行时间的有效任务的time最后一点注意该类扩展直Tasking――提供周期性调用的task()本应用程序中它用于不断的调用Presentation的idle()方法并使其循环工作你在本书中学习到也就是Movies所需要的但是此任务几乎一直都自动的为你所操作使用Presentation并不好运(或者为此事而捕获但有些离开本主题了

boolean broadcasting = false;public static final int BROADCAST_WIDTH = ;public static final int BROADCAST_HEIGHT = ;Button startStopButton;Button configButton;Presentation pres;int presenterTimeScale = ;

这些是服务器的实例变量是一个用于指定当开始/停止按钮按下的时候做什么的标记下面是一对广播视频大小的常量紧跟着是服务器GUI的按钮最后是一个Presentation对象以及它的时间尺度(媒体的保持时间系统一个的时间尺度表示一秒种里有个单位也是QuickTime中默认的

public static void main (String[] args) {

Systemoutprintln (main);

try {

QTSessionopen();

new LittleBroadcast();

} catch (QTException qte) {

qteprintStackTrace();

}

}

在这个main中并没有什么独特的地方我投入了所有的精力在构造函数上以防止为那些我需要的实例创建一个内部类如果你扩展本代码你可能会发现这很有用

public LittleBroadcast() throws QTException {

Systemoutprintln (LittleBroadcast constructor);

QTFile file = new QTFile (new File (littlesdp));

try {

MediaParams mediaParams = new MediaParams();

mediaParamssetWidth (BROADCAST_WIDTH);

mediaParamssetHeight (BROADCAST_HEIGHT);

QDGraphics myGWorld =

new QDGraphics (new QDRect (

BROADCAST_WIDTH BROADCAST_HEIGHT));

mediaParamssetGWorld (myGWorld);

PresParams presParams =

new PresParams( presenterTimeScale

QTSConstantskQTSSendMediaFlag |

QTSConstantskQTSAutoModeFlag |

QTSConstantskQTSDontShowStatusFlag

mediaParams );

pres = PresentationfromFile(file presParams );

构造函数的第一事是装载名为littlesdp的SDP文件 但这并不是所有的都需要创建Presentation ――在调用PresentationfromFile()的时候需要服务器应用程序设置一些必要的参数首先你要创建一个MediaParams对象这样你能设置视频的高度和宽度您必须做的其它重要事是提供照相机一个图形界面由QDGraphics创建MediaParams设置是的 名字是古怪的 因为QTJ 设计员想注重与AWT Graphics对象的相似性 但得到或设置这样的对象的用途的所有方法是使用其本地API名字 GWorld 最后 你为所有的Presentation创建一个PresParams来设置参数 这采取一个有些任意的时标 一些算术上的行为标记彼此ORed 以及MediaParams 可能的行为标记 都被定义在QTSConstants 包括:

·KQTSAutoModeFlag: 都使用默认值 最重要地 这些使用默认值Sourcer Presentation的来源是从各种各样的输入装置执行获取的SequenceGrabber 它还可能播放一个在磁盘上或是任意目录下的的QuickTime 文件; 稍后我将探讨这些问题

·KQTDontShowStatusFlag: 不要创建一个会导致连接数和状态信息总被显示在客户端的流媒体状态处理程序

·KQTSSendMediaFlag:发送不接收数据

·KQTSReceiveMediaFlag接收不发送数据

在SDP文件说明 参数 以及GWorld 设置下 创建Presentation和PresentationfromFile()

// find audio streamStream audioStream = null;

for (int i=; i<=presgetNumStreams(); i++) {

Systemoutprintln (stream: + i + : +

presgetIndStream(i));

Stream aStream = presgetIndStream (i);

if (preshasCharacteristic(aStream

StdQTConstantsaudioMediaCharacteristic)) {

audioStream = aStream;

break;

}

}

Systemoutprintln (audioStream = + audioStream);

pressetVolumes (audioStream );

Systemoutprintln (created presentation gworld == +

presgetGWorld() + size == +

mediaParamsgetWidth() + x +

mediaParamsgetHeight() + streams == +

presgetNumStreams());

//*******这不是真正地必要的 但它将告诉你怎么通过Presentation游览来挑选各自的流媒体 PresentationgetIndStream 会由索引返回一个Stream(附注QuickTime 索引都是基于)它重复这些audioMediaCharacteristic 请求查找音频流 (对于录影 您就要请求visualMediaCharacteristic) 这个实例在audioStream上为左右声道设置音量最大值为

最后 println从Presentation和MediaParams转存一些有意义的元数据

配置Presentation

SettingsDialog sd = new SettingsDialog (pres);

Systemoutprintln (Did settings);

prespreroll();

broadcasting = false;

这是设置presentation最后的步骤 SettingsDialog存在用户以输入装置选择的音频和视频 (二个流媒体SDP 文件被指定在Presentation中) 每个流媒体都可由一个压缩格式来定制 (MPEG Sorenson Video H 等) 以及一个分包器(有时由压缩格式定义; 可观察它是否随着压缩格式的改变而自动改变) 这个GUI实例显示在图

图像 为一个Presentation SettingsDialog

在此图中 音频默认为计算机连线输入更改它为iSight 您需要点击Source按钮提出的可选设备列表显示在图

来源选择对话框

最后就是调用Presentationpreroll() 如同Moviepreroll() 提供Presentation一个机会预先分配资源以及准备好开始流媒体Presentation

提供一个控制GUI

// Make monitor window

startStopButton = new Button (Start);

configButton = new Button (Configure);

startStopButtonaddActionListener (this);

configButtonaddActionListener (this);

Frame monitorFrame = new Frame (QTJ Streaming);

monitorFramesetLayout (new BorderLayout());

Panel buttonPanel = new Panel();

buttonPaneladd (startStopButton);

buttonPaneladd (configButton);

monitorFrameadd (buttonPanel BorderLayoutSOUTH);

monitorFramepack();

monitorFramesetVisible(true);

这个为控制和配置Presentation设置了很小的GUI提供基本的一个起始/停止键和一个配置按钮 按钮作为一个ActionListener提交给this 意味着这个类将需要提供一个actionPerformed方法来处理按钮点击 控制GUI 的屏幕截图显示在图

监控/控制窗体

在这点上你也许会问一个有趣的问题: 从什么时候我们开始关心使用GUI提供server?据推测 这是从Classic Mac OS开始的一个传统 它没有一个用命令行启动和传递参数的程序但此外 你通常会希望提供一个流媒体数据的预览 并且如果您有一个预览视窗 为什么会也没有一个配置的GUI?

无论如何 这是大概的讨论 因为QTJ 不提供您能使用来预览的一个AWT Component在有些方面有希望的是 QTFactory将得到一个新的超负荷为采用一个Presentation并且返回一个显示流媒体视频的Component的makeQTComponent 它大概可能使用一些QuickDraw voodoo而完全放弃Java的东西 如果在各task()中回调(参见下面) 您采取GWorld被及早创建 转换它成Pict 并且作为一份唯一命名的文件 您会看见每一个都是不同的 意味GWorld每次都得到新数据 所以如果您替换掉GWorld 改为能给AWT Component在各通道上定义象素 您会有银幕上的预览何人有胆量如此做? 在quicktimejava list上查看

详细资料

// add shutdown handler to make sure presentation

// gets stopped

Thread presentationStopper = new Thread() {

public void run() {

try {

presstop();

} catch (QTException qte) {}

}

};

RuntimegetRuntime()addShutdownHook (presentationStopper);

这个关闭异常分支确定Presentation在程序退出之前被终止 这是重要的原因 象SequenceGrabber Presentation愉快继续运行在您的应用程序退出之后绑定一个端口严重的循环 使用您的获取设备保留其它应用程序

} catch ( QTException e ) {

eprintStackTrace();

Systemexit ();

}

}

最后 构造函数捕捉并抛出所有的QTExceptions

public void actionPerformed (ActionEvent ae) {

Systemoutprintln (actionPerformed);

try {

if (aegetSource() == startStopButton) {

if (broadcasting) {

presstop();

stopTasking();

broadcasting = false;

startStopButtonsetLabel (Start);

Systemoutprintln (Stopped);

} else {

presstart();

startTasking();

broadcasting = true;

startStopButtonsetLabel (Stop);

Systemoutprintln (Started);

}

} else if (aegetSource() == configButton) {

new SettingsDialog (pres);

}

} catch (QTException qte) {

qteprintStackTrace();

}

}

这是非常直接的处理起始/终止和设置按钮如果点击的按钮是起始/终止 设置GUI 就会调用在Presentation上的 start() 或stop() 开始或停止正在执行的任务 (定期回调这个类的task()方法) 为下按钮点击目的设置broadcasting标志 并且更改按钮标签如果用户点击了配置 它生成为Presentation新的SettingsDialog

public synchronized final void task() throws QTException {

presidle(null);

}}

最后的这个方法实现了继承自Tasking的task()方法并且被在操作开始按钮调用startTasking()后定时的调用使用简单的调用Presentationidle() 它提供了表达时间来从获取设备取得当前数据对其编码并传输流出去

运行流媒体客户端

最简单的使客户机看广播的方法是使用QuickTime 播放器打开服务器使用并创建Presentation的同样SDP 文件这将调用SDP 输入程序连接到流媒体并且开始分析这些内容注意客户机和服务器不能在同一台机器上 明显地因为服务器为使用表示而占用端口 拒绝客户机对这些端口的使用 显示在我的计算机上的流媒体的外观(那是正在播放我的Macross和Escaflowne玩具)

QuickTime流媒体客户端

如果您使用QuickTime 播放器 您能使用其得到信息指令显示两种流媒体以及他们的格式 在表 您能看有二种媒体: 一条未压缩的kHz 音频流 和一条H 的视频流

客户端信息窗体

结语

对我来说播放基于Java的QuickTime流媒体比想象的容易多了 最简单的例子 从获取设备播放 只需要少于 个代码行 显然 最困难的部份是了解SDP文件 它被证明是非常的过分讲究并且它的说明文件包含大量应用程序级别程序员不会有的知识同样不幸的是QTJ不再提供预览组件 但也许在将来会提供 以及一小段的GWorld/QuickDraw 堆砌也许会在将来制造出这样的组件

本文只包括怎么为实时获取数据设置广播其它可利用的Sourcers 譬如来自磁盘或任意目录种的那些广播QuickTime 文件 将会在以后的部分中讨论               

上一篇:JAVA调用exe可执行文件

下一篇:Java内存结构与模型