基于JMF视频聊天

java media framework  (JMF )为java 在多媒体领域的开发提供了便利的平台。本文基于JMF 遵从RTP 协议对音/视频聊天进行了实现,解决了会话的管理和流媒体的发送、接收、播放等关键问题。

1.引言

如今VoIP  已经成为一种经济高效的通讯方式。不过只有语音方面的交互已经不能满足人们的需求。只闻其声不见其人显得过于单调。音/视频聊天在这种需求下应运而生。音/视频聊天的关键是实时性和同步。因此RTP(实时传输协议)就作为其底层传输协议的不二之选。Sun公司所推出的java 凭借其面向对象、跨平台、安全、多线程等性能成为现今最流行的网络编程语言。Sun在java 的基础上针对多媒体的播放与传输开发了JMF。本文基于JMF对音/视频聊天进行实现。

2.   RTP与JMF

2. 1 RTP与RTCP RTP 是由IETF  的AVT 工作组针对流媒体开发的协议,它位于传输层之上但并不要求 传输层协议使用UDP(用户数据报协议),不过迄今为止UDP 是最常用的RTP 的底层协议。由于UDP 是面向无连接的不可靠协议,这使其更适于流媒体传输。UDP 作为轻量级的传输协议并不保证传输质量但实现了高效的盲目传输。QOS  由UDP  的上层协议RTP  实现。针对UDP 的无连接传输所导致的误码、丢失、乱序问题,RTP 规定了其组成。RTP 包由两部分组成:包头和负载。包头中含有负载类型、序号、时间戳、同步源标识等。负载存储了包头指定的流媒体数据。 RTCP  (实时传输控制协议)为RTP 连接提供了QOS 信息并维持各个参与者的状态信息。RTCP 共有五个组成部分:RR (接收端报文)、SR (发送端报文)、SDES (信源描述)、 BYE(结束报文)和AS(应用程序特定)。其中RR 是由RTP 包的接收端发给每一个参与者的接收反馈报文。报文中包含丢包数、所接受到RTP  包的最高序号以及用来估算发送端与接收端之间延迟的时间信息。这些信息可供参与者估计当前网络状况进行拥塞控制保证QOS。SR 是由RTP  发送端发给接收端。发送端报文包含发送端所发送的总包数与字节数以及时间同步信息。并把发往接收端的RR 内嵌其中。SDES  是为参与者定义的一个规范名。还有一些其它识别信息,如e-mail、电 话等信息。参与者依靠SSRC 与标准名的映射关系完整多媒体流的复用与解复用。BYE  作为一个参与者退出会话的结束报文,其中包含了自定义的退出原因。当一个参与者退出所在会话时,它将发送BYE 给会话中的所有参与者。AS 则为应用程序传送信息定义了一种方式。 RTP 有一个虚拟环境—— Session(会话),是由多个参与者所组成的联盟。每个参与者都设定一个RTP 端口和一个RTCP 端口。每当有参与者加入会话都会发出RTCP 报文通知会话中的其它参与者。一个参与者可加入多个会话,接收端根据参与者的规范名对参与者在不同会话中的媒体流进行同步。RTCP 包是一个复合包,它至少含有两个包。一般第一个包为报文(SR 或RR),第二个包为源描述(SDES)。如果退出会话还要在SDES 包后面添加结束报文(BYE)。

JMF对RTP协议进行了包装,为流媒体的传输及播放提供了简洁便利的应用程序接口。 JMF为会话管理、参与者、媒体流及RTP事件监听都设定了对应类。利用JMF进行流媒体的发送、接收、播放基本框架

JMF 流媒体传输框架 JMF  针对 RTP   的媒体传输类型相当有限,只有一种内容描(ContentDescriptor ) RAW_RTP。JMF 所支持的RTP 负载类型也非常有限

视频发送端

import java.awt.Dimension;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;

import javax.media.CannotRealizeException;
import javax.media.CaptureDeviceInfo;
import javax.media.CaptureDeviceManager;
import javax.media.Codec;
import javax.media.Control;
import javax.media.Format;
import javax.media.Manager;
import javax.media.NoDataSourceException;
import javax.media.NoPlayerException;
import javax.media.NoProcessorException;
import javax.media.Owned;
import javax.media.Player;
import javax.media.Processor;
import javax.media.control.QualityControl;
import javax.media.control.TrackControl;
import javax.media.format.UnsupportedFormatException;
import javax.media.format.VideoFormat;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.DataSource;
import javax.media.protocol.PushBufferDataSource;
import javax.media.protocol.PushBufferStream;
import javax.media.protocol.SourceCloneable;
import javax.media.rtp.InvalidSessionAddressException;
import javax.media.rtp.RTPManager;
import javax.media.rtp.SendStream;
import javax.media.rtp.SessionAddress;
import javax.swing.JFrame;

import jmapps.util.StateHelper;

public class Video extends Thread{

    private SessionAddress local = null;
    private SessionAddress target = null;
    private boolean flag =true;
    
//    public static void main(String[] args) {    
//        try {
//            new Audio(InetAddress.getLocalHost().getHostAddress(),InetAddress.getLocalHost().getHostAddress());
//        } catch (UnknownHostException e) {
//            e.printStackTrace();
//        }
//    }
    public Video(String localhost, String targethost) {
        try {
            local = new SessionAddress(InetAddress.getByName(localhost),60002);
            target = new SessionAddress(InetAddress.getByName(targethost),50002);
        } catch (UnknownHostException e1) {
            e1.printStackTrace();
        }
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    public void run() {
        
        CaptureDeviceInfo info = (CaptureDeviceInfo) CaptureDeviceManager.getDeviceList(new VideoFormat(null)).get(0);//捕捉设备
        DataSource ds = null;
        try {
            ds = Manager.createDataSource(info.getLocator());//定位捕捉媒体源
        } catch (NoDataSourceException | IOException e2) {
            // TODO Auto-generated catch block
            e2.printStackTrace();
        }
        DataSource tem = Manager.createCloneableDataSource(ds);//克隆数据源
        
        Processor p = null;
        try {
            p = Manager.createProcessor(((SourceCloneable)tem).createClone());//将克隆数据源用于发送
        } catch (NoProcessorException | IOException e2) {
            e2.printStackTrace();
        }
        StateHelper sh = new StateHelper(p);//是processor发送阻塞,必须在sh设置的时间间隔后才将数据发送出去
        doSomeVideoProcess(p,sh);
        RTPManager mgr = RTPManager.newInstance();
        try {
            mgr.initialize(local);//本地地址
            mgr.addTarget(target);//目的地址
        } catch (InvalidSessionAddressException | IOException e) {
            e.printStackTrace();
        }
        JFrame jf = new  JFrame();
        jf.setBounds(0, 200, 200, 200);
        Player player = null;
        try {
            player = Manager.createRealizedPlayer(tem);//播放数据源
        } catch (NoPlayerException | CannotRealizeException | IOException e1) {
            e1.printStackTrace();
        }  
        player.start();
        jf.add(player.getVisualComponent());
        jf.setVisible(true);
        jf.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        
        jf.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                flag=false;
            }
        });
        
        DataSource transmitDS = p.getDataOutput();
        PushBufferDataSource pbds = (PushBufferDataSource)transmitDS;
        PushBufferStream pbss[] = pbds.getStreams();
        System.out.println(pbss.length);
        for(int i=0;i<pbss.length;i++) {
            try {
                SendStream ss = mgr.createSendStream(transmitDS, i);
                ss.start();
            } catch (UnsupportedFormatException | IOException e) {
                e.printStackTrace();
            }
        }
        p.start();
        while(flag) {} //让流媒体数据持续下去
        if(p!=null) {
            p.stop();
            p.close();
        }
        if(player!=null) {
            player.stop();
            player.close();
        }
        jf.dispose();
        if(mgr!=null) {
            mgr.removeTargets("client disconnnected");
            mgr.dispose();
            mgr = null;
        }
    }    
    private static void doSomeVideoProcess(Processor p,StateHelper sh) {
        sh.configure(5000);
        p.setContentDescriptor(new ContentDescriptor(ContentDescriptor.RAW_RTP));
        setVideoTrackFormat(p.getTrackControls());        
        sh.realize(5000);
        setJPEGQuality(p, 0.5f);
    }
    private static Format checkForVideoSizes(Format original, Format supported) { //设置样式和发送大小
        int width, height;
        Dimension size = ((VideoFormat)original).getSize();
        Format jpegFmt = new Format(VideoFormat.JPEG_RTP);
        Format h263Fmt = new Format(VideoFormat.H263_RTP);
        if (supported.matches(jpegFmt)) {
            
            width = (size.width % 8 == 0 ? size.width :
                    (int)(size.width / 8) * 8);
            height = (size.height % 8 == 0 ? size.height :
                    (int)(size.height / 8) * 8);
        } else if (supported.matches(h263Fmt)) {
           
            if (size.width < 128) {
                width = 128;
                height = 96;
            } else if (size.width < 176) {
                width = 176;
                height = 144;
            } else {
                width = 352;
                height = 288;
            }
        } else {
            return supported;            
        }
        return (new VideoFormat(null, 
                new Dimension(width, height), 
                Format.NOT_SPECIFIED,
                null,
                Format.NOT_SPECIFIED)).intersects(supported);
    }
    private static boolean setVideoTrackFormat(TrackControl[] tracks) {
        if(tracks==null || tracks.length<1)
            return false;
        boolean atLeastOneTrack = false;
        for(TrackControl t:tracks) {
            if(t.isEnabled()) {
                Format[] supported = t.getSupportedFormats();
                Format chosen = null;
                if(supported.length>0) {
                    if(supported[0] instanceof VideoFormat)
                        chosen = checkForVideoSizes(t.getFormat(),supported[0]);
                    else
                        chosen = supported[0];                    
                    t.setFormat(chosen);
                    atLeastOneTrack = true;
                } else {
                    t.setEnabled(false);
                }
            } else {
                t.setEnabled(false);
            }
        }
        return atLeastOneTrack;
    }    
    private static void setJPEGQuality(Processor p, float val) {
        Control cs[] = p.getControls();
        QualityControl qc = null;
        VideoFormat jpegFmt = new VideoFormat(VideoFormat.JPEG);
        
        for (int i = 0; i < cs.length; i++) {
            if (cs[i] instanceof QualityControl && cs[i] instanceof Owned) {
            Object owner = ((Owned)cs[i]).getOwner();
            
            if (owner instanceof Codec) {
                Format fmts[] = ((Codec)owner).getSupportedOutputFormats(null);
                for (int j = 0; j < fmts.length; j++) {
                    if (fmts[j].matches(jpegFmt)) {
                        qc = (QualityControl)cs[i];
                        qc.setQuality(val);                    
                        break;
                    }
                }
            }
            if (qc != null)
                break;
            }
        }
    }
}
接收端
import java.awt.BorderLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;

import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.Manager;
import javax.media.NoPlayerException;
import javax.media.Player;
import javax.media.RealizeCompleteEvent;
import javax.media.control.BufferControl;
import javax.media.protocol.DataSource;
import javax.media.rtp.InvalidSessionAddressException;
import javax.media.rtp.RTPManager;
import javax.media.rtp.ReceiveStream;
import javax.media.rtp.ReceiveStreamListener;
import javax.media.rtp.SessionAddress;
import javax.media.rtp.event.ByeEvent;
import javax.media.rtp.event.NewReceiveStreamEvent;
import javax.media.rtp.event.ReceiveStreamEvent;
import javax.swing.JFrame;

import view.MyJframe;

public class Receivevideo extends MyJframe implements ReceiveStreamListener,ControllerListener{
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	Player player;
	RTPManager mgr;
//	public static void main(String[] args) {
//			try {
//				new Receivevideo(InetAddress.getLocalHost().getHostAddress(),InetAddress.getLocalHost().getHostAddress());
//			} catch (UnknownHostException e) {
//				// TODO Auto-generated catch block
//				e.printStackTrace();
//			}	
//	}
//	
	public Receivevideo(String localhost, String targethost,Video video) {
		
		SessionAddress local = null;
		SessionAddress target = null;
		try {
			local = new SessionAddress(InetAddress.getByName(localhost),50002);
			target = new SessionAddress(InetAddress.getByName(targethost),60002);
		} catch (UnknownHostException e1) {
			e1.printStackTrace();
		}
		
		mgr = RTPManager.newInstance();//rtp协议
		mgr.addReceiveStreamListener(this);
		try {
			mgr.initialize(local);
			mgr.addTarget(target);
		} catch (InvalidSessionAddressException | IOException e) {
			e.printStackTrace();
		}
		BufferControl bc = (BufferControl)mgr.getControl("javax.media.control.BufferControl");//捕捉播放器
        if (bc != null)
            bc.setBufferLength(350);
        addWindowListener(new WindowAdapter() {
        	@Override
        	public void windowClosing(WindowEvent e) {
        		disconnect();
        		if(video!=null) {
        			video.setFlag(false);
        		}
        	}
		});
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        setVisible(true);
	}
	@Override
	public void update(ReceiveStreamEvent e) { //数据监听
		if(e instanceof NewReceiveStreamEvent) {
			ReceiveStream rs = ((NewReceiveStreamEvent)e).getReceiveStream();
			DataSource ds = rs.getDataSource();
			try {
				player = Manager.createPlayer(ds);
			} catch (NoPlayerException | IOException e1) {
				e1.printStackTrace();
			}
			player.addControllerListener(this);//设置播放控制器
			player.start();			
		} else if(e instanceof ByeEvent) {
			disconnect();
		}
	}
	public void disconnect() {
		if(player!=null) {
			player.stop();
			player.close();
		}
		if(mgr!=null) {
			mgr.removeTargets("closing session");
			mgr.dispose();
			mgr = null;
		}		
	}
	@Override
	public void controllerUpdate(ControllerEvent e) {
		if(e instanceof RealizeCompleteEvent) {
			if(player.getVisualComponent()!=null)
				add(player.getVisualComponent());
			if(player.getControlPanelComponent()!=null)
				add(player.getControlPanelComponent(),BorderLayout.SOUTH);
			pack();
		}
	}
}
附注:须按装JMF,JMF只支持JDK32位
详情参考: 点击打开链接

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木羊子羽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值