1. camera YUV420数据直接socket发送到服务器端,转换成RGB显示 占用带宽较多。
原理阐述:基于socket,wifi。 android客户端获取的帧数据的格式为yuv420格式,本来将此data数据流直接传输到另一android端,由接收端完成图像的解码转换工作,但是发现如果直接传数据流数据量太大,还不如直接在本地解码转换完毕生成图片,然后将图片传到另外一android端进行重绘排列。
考虑到效率,可将核心的解码转换封装成c实现.so,速度好些
改进: 项目采用多播协议,即如果在多台电脑上同时运行服务端,则一台手机可以同时控制多台电脑做同样的事情,同时操作所有开启服务端电脑的ppt。
“ 谈下实现思路:
1 socket实现传输,目前网络用的wifi
2 java端的视频获取我采用的是jmf框架
3 逻辑比较简单:总体实现无非就是android端视频捕获发送yuv原格式到pc端进行解码而后在pc上显示,
同时pc通过jmf上获取camera摄像头的数据,通过socket传输到android端进行重绘。
缺点:
1)"
你这个实现方案流量大死了,根本就不用,代码还当宝一样。。
网上都有代码了,你还不是根据别人的改的,鄙视你。"
http://www.eoeandroid.com.sixxs.org/thread-51460-4-1.html
"
你这就是做着玩的,离视频聊天差远了。
实现方案是:移植ffmpeg获取原始数据,编码,发送流媒体服务器,接收数据,解码,显示。流量10kb左右。不是我喷你,你不公开代码你就做好点在来炫耀,要不你炫耀个毛线。"
2) "你这也好意思叫视频聊天,视频有了,声音呢?你实现的只是捕获YUV数据然后绘制图片而已吧~~~~"
2. RTP SDP结合+
The following network protocols are supported for audio and video playback:
RTSP (RTP, SDP)
HTTP/HTTPS progressive streaming
HTTP/HTTPS live streaming draft protocol:
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
目前可移植的有,android的opencore编码,参考http://www.shouyanwang.org/thread-184-1-1.html。
ffmpeg编码,这个网上很难找,但是也有人实现哦。(编码效率好,但是支持不了高清视频)
编码完成可以通过rtp协议发送。就可以视频啦。rtp协议什么的就不说了哦。
以上方法的代码,在这个链接最好回复我贴了代码,不是完整的。链接:http://www.eoeandroid.com/thread-51460-1-1.html
第2种方法,也就是我用的方法。
通过MediaRecorder录制。然后绑定一个localsocket,可以获取编码后的视频数据传输;
代码如下:
计算 AVCDecoderConfigurationRecord 得到 CodecPrivateData 数据
H.264 视频流的CodecPrivateData 实际上就是 AVCDecoderConfigurationRecord 中 SequenceParameterSets(SPS)和 PictureParameterSets(PPS)使用 byte[] {00, 00, 01} 连接的字节数组。
注意!FLV 文件中第一个 VIDEOTAG 的 VIDEODATA 的 AVCVIDEOPACKET 的 Data 总是 AVCDecoderConfigurationRecord(在 ISO/IEC 14496-15 中定义),解码的时候注意跳过这个 VIDOETAG。
AVCDecoderConfigurationRecord 结构的定义:
aligned(8) class AVCDecoderConfigurationRecord {
unsigned int(8) configurationVersion = 1;
unsigned int(8) AVCProfileIndication;
unsigned int(8) profile_compatibility;
unsigned int(8) AVCLevelIndication;
bit(6) reserved = ‘111111’b;
unsigned int(2) lengthSizeMinusOne;
bit(3) reserved = ‘111’b;
unsigned int(5) numOfSequenceParameterSets;
for (i=0; i< numOfSequenceParameterSets; i++) {
unsigned int(16) sequenceParameterSetLength ;
bit(8*sequenceParameterSetLength) sequenceParameterSetNALUnit;
}
unsigned int(8) numOfPictureParameterSets;
for (i=0; i< numOfPictureParameterSets; i++) {
unsigned int(16) pictureParameterSetLength;
bit(8*pictureParameterSetLength) pictureParameterSetNALUnit;
}
}
下面蓝色的部分就是 FLV 文件中的 AVCDecoderConfigurationRecord 部分。
00000130h: 00 00 00 17 00 00 00 00 01 4D 40 15 FF E1 00 0A ; .........M@.?.
00000140h: 67 4D 40 15 96 53 01 00 4A 20 01 00 05 68 E9 23 ; gM@.朣..J ...h?
00000150h: 88 00 00 00 00 2A 08 00 00 52 00 00 00 00 00 00 ; ?...*...R......
根据 AVCDecoderConfigurationRecord 结构的定义:
因此 CodecPrivateData 的字符串表示就是 000001674D4015965301004A2000000168E9238800
但是设置 MediaStreamAttributeKeys.CodecPrivateData 是没用的(只有 H.264 是这样,其他类型的视频流仍然需要设置),只有将 CodecPrivateData 写入 H.264 视频流第一帧数据的前面 Silverlight 才能正常解码。
也就是说,Silverlight 的 H.264 解码器会读取第一帧前面的 CodecPrivateData 数据来进行配置。
因为 CodecPrivateData 数据已经包含视频流的解码器参数(包括视频的宽高),所以就不需要设置 MediaStreamAttributeKeys.CodecPrivateData、MediaStreamAttributeKeys.Width 和 MediaStreamAttributeKeys.Height 了。
计算 NALU 得到帧数据
FLV 文件中 VIDEOTAG 的 VIDEODATA 的 AVCVIDEOPACKET 的 Data 不是原始视频帧数据,而是一个或更多个 NALU 数据片段。在这篇文章中,你认为 H.264 视频帧数据是由多个 NALU 组成的。当然实际上并不是这样,关于这部分的概念请自行 Google,本文将不做讨论。
下面是 FLV 文件中 VIDEOTAG 的 VIDEODATA 的 AVCVIDEOPACKET 的 Data 属性的数据(第一帧数据)。
00000300h: 00 00 00 00 00 17 01 00 00 22 00 00 00 31 65 88 ; ........."...1e?
00000310h: 80 40 05 B7 95 53 67 FF 84 6C 07 EB 00 F8 45 FB ; €@.窌Sg刲.?鳨?
00000320h: F9 15 71 0D A4 C5 2C 00 00 03 00 00 03 00 3F 2B ; ?q.づ,.......?+
00000330h: 5B 06 57 48 29 F4 08 00 00 0A 10 02 D0 7A FE 00 ; [.WH)?.....衵?
00000340h: 00 00 38 65 01 22 22 01 00 17 B7 95 53 67 FF 84 ; ..8e.""...窌Sg?
00000350h: 6C 07 EB 00 F8 45 FB F9 15 71 0D A4 C5 2C 00 E8 ; l.?鳨.q.づ,.?
00000360h: F3 37 75 43 90 00 00 03 00 15 EF AA A8 53 86 01 ; ?uC?....铼⊿?
00000370h: DD 57 60 00 00 03 01 59 0C F4 3C 00 00 00 33 65 ; 軼`....Y.?...3e
00000380h: 00 90 88 80 40 05 B7 95 53 67 FF 84 6C 07 EB 00 ; .悎€@.窌Sg刲.?
00000390h: F8 45 FB F9 15 71 0D A4 C5 2C 00 00 03 00 00 03 ; 鳨.q.づ,......
000003a0h: 00 3F 2B 5B 06 57 48 29 F4 08 00 00 0A 10 02 D0 ; .?+[.WH)?.....?
000003b0h: 7A FE 00 00 00 38 65 00 D8 88 80 40 05 B7 95 53 ; z?..8e.貓€@.窌S
000003c0h: 67 FF 84 6C 07 EB 00 F8 45 FB F9 15 71 0D A4 C5 ; g刲.?鳨.q.づ
000003d0h: 2C 00 E8 F3 37 75 43 90 00 00 03 00 15 EF AA A8 ; ,.梵7uC?....铼?
000003e0h: 53 86 01 DD 57 60 00 00 03 01 59 0C F4 3C 00 00 ; S?軼`....Y.?..
000003f0h: 00 F4 08 00 01 33 00 00 17 00 00 00 00 AF 01 27 ; .?..3.......?'
帧数据是将多个 NALU 使用 byte[] {00, 00, 01} 连接的字节数组。
byte[] = {
00,00,01,65,88,
80,40,05,B7,95,53,67,FF,84,6C,07,EB,00,F8,45,FB,
F9,15,71,0D,A4,C5,2C,00,00,03,00,00,03,00,3F,2B,
5B,06,57,48,29,F4,08,00,00,0A,10,02,D0,7A,FE,
00,00,01,65,01,22,22,01,00,17,B7,95,53,67,FF,84,
6C,07,EB,00,F8,45,FB,F9,15,71,0D,A4,C5,2C,00,E8,
F3,37,75,43,90,00,00,03,00,15,EF,AA,A8,53,86,01,
DD,57,60,00,00,03,01,59,0C,F4,3C,
00,00,01,65,
00,90,88,80,40,05,B7,95,53,67,FF,84,6C,07,EB,00,
F8,45,FB,F9,15,71,0D,A4,C5,2C,00,00,03,00,00,03,
00,3F,2B,5B,06,57,48,29,F4,08,00,00,0A,10,02,D0,
7A,FE,
00,00,01,65,00,D8,88,80,40,05,B7,95,53,
67,FF,84,6C,07,EB,00,F8,45,FB,F9,15,71,0D,A4,C5,
2C,00,E8,F3,37,75,43,90,00,00,03,00,15,EF,AA,A8,
53,86,01,DD,57,60,00,00,03,01,59,0C,F4,3C
};
如果是第一帧数据,那么前面还要加上 CodecPrivateData 数据。
byte[] = {
00,00,01,67,4D,40,15,96,53,01,00,4A,20,
00,00,01,68,E9,23,88,00,
00,00,01,65,88,
80,40,05,B7,95,53,67,FF,84,6C,07,EB,00,F8,45,FB,
F9,15,71,0D,A4,C5,2C,00,00,03,00,00,03,00,3F,2B,
5B,06,57,48,29,F4,08,00,00,0A,10,02,D0,7A,FE,
00,00,01,65,01,22,22,01,00,17,B7,95,53,67,FF,84,
6C,07,EB,00,F8,45,FB,F9,15,71,0D,A4,C5,2C,00,E8,
F3,37,75,43,90,00,00,03,00,15,EF,AA,A8,53,86,01,
DD,57,60,00,00,03,01,59,0C,F4,3C,
00,00,01,65,
00,90,88,80,40,05,B7,95,53,67,FF,84,6C,07,EB,00,
F8,45,FB,F9,15,71,0D,A4,C5,2C,00,00,03,00,00,03,
00,3F,2B,5B,06,57,48,29,F4,08,00,00,0A,10,02,D0,
7A,FE,
00,00,01,65,00,D8,88,80,40,05,B7,95,53,
67,FF,84,6C,07,EB,00,F8,45,FB,F9,15,71,0D,A4,C5,
2C,00,E8,F3,37,75,43,90,00,00,03,00,15,EF,AA,A8,
53,86,01,DD,57,60,00,00,03,01,59,0C,F4,3C
};
录制完成后保存视频,然后用WinHex打开。如果你是手机需要的视频就录制3gp格式。
然后CTRL+F搜索avc.可以看到如图:
把鼠标点击到avc附近,左边也就在01这个数据附近了。
好了avc box的内容就是从01开始,比如上图就是:
{ 0x01, 0x42, (byte) 0x80, 0x0A,(byte) 0xFF,
(byte) 0xE1, 0x00, 0x12, 0x67, 0x42, (byte) 0x80, 0x0A,
(byte) 0xE9, 0x02, (byte) 0xC1, 0x29, 0x08, 0x00, 0x00,
0x1F, 0x40, 0x00, 0x04, (byte) 0xE2, 0x00, 0x20, 0x01,
0x00, 0x04, 0x68, (byte) 0xCE, 0x3C, (byte) 0x80 };
sps信息就是67开头 67-20 pps就是68-80 具体数据意义可以看上篇博客。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
昨天经过编译配置live555MediaServer.exe,然后通过VLC播放rtsp://88.88.88.200:554/1.mp3后,本以为很简单的就可以通过Android的VideoView播放音频。很纠结的提示不支持。
经过一天的搜索,得出了结论。
官方文档:Android Supported Media Formats提到
The following network protocols are supported for audio and video playback:
RTSP (RTP, SDP)
HTTP/HTTPS progressive streaming
HTTP/HTTPS live streaming draft protocol:
MPEG-2 TS media files only
Protocol version 3 (Android 4.0 and above)
Protocol version 2 (Android 3.x)
Not supported before Android 3.0
第一条说明了支持RTSP后面包含了RTP和SDP流。其他的流可能暂时不支持。
而对于网上一个代码例子
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
LinearLayout
xmlns:android
=
"http://schemas.android.com/apk/res/android"
android:layout_width
=
"fill_parent"
android:layout_height
=
"fill_parent"
android:orientation
=
"vertical"
>
<
TextView
android:layout_width
=
"fill_parent"
android:layout_height
=
"wrap_content"
android:text
=
"@string/hello"
/>
<
VideoView
android:id
=
"@+id/videoView1"
android:layout_width
=
"match_parent"
android:layout_height
=
"wrap_content"
/>
LinearLayout
>
|
package
com.jouhu.live;
import
android.app.Activity;
import
android.net.Uri;
import
android.os.Bundle;
import
android.widget.VideoView;
public
class
Live555RTSPActivity
extends
Activity {
/** Called when the activity is first created. */
VideoView videoView ;
@Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.main);
videoView = (VideoView)findViewById(R.id.videoView1);
PlayRtspStream(
"rtsp://218.204.223.237:554/live/1/66251FC11353191F/e7ooqwcfbqjoo80j.sdp"
);
}
private
void
PlayRtspStream(String rtspUrl)
{
videoView = (VideoView)findViewById(R.id.videoView1);
videoView.setVideoURI(Uri.parse(rtspUrl));
videoView.requestFocus();
videoView.start();
}
}
|
从代码编译结果,的确可以显示出来。跟后面使用的SDP应该有关。
然后开始搜索如何生成SDP的问题。
使用FFmpeg可以生成相应的SDP文件,相应的命令行为
F:\AndroidDevelop\LiveVideoSolution\FFmpeg-FFServer\ffmpeg-git-d049257-win32-static\bin>ffmpeg -fflags +genpts -re -i 1.avi -vcodec copy -an -f rtp rtp://127.0.0.1:10000 -vn -acodec copy -f rtp rtp://127.0.0.1:20000 > 2.sdp
SDP:
v=0
o=- 0 0 IN IP4 127.0.0.1
s=No Name
t=0 0
a=tool:libavformat 53.17.0
m=video 10000 RTP/AVP 96
c=IN IP4 127.0.0.1
a=rtpmap:96 H264/90000
a=fmtp:96 packetization-mode=1; sprop-parameter-sets=Z2QAM6w07AgDHlkwEQAAAwPpAAC7gI8YMTg=,aO68sA==
m=audio 20000 RTP/AVP 14
c=IN IP4 127.0.0.1
b=AS:48
|
参考文章:
1 http://www.cuitu.net/content/ffmpegzai-windowsxia-bu-huo-she-xiang-tou-shi-pin
2 http://developer.android.com/guide/appendix/media-formats.html
目前在做视频应用的时候,比较先进的技术就是RTSP流媒体了,那么如果利用Android的播放控件VideoView来播放RTSP的流呢?
RTSP流媒体链接:
http://218.204.223.237:8081/wap/
这个链接含有所有的RTSP流媒体的链接,现在咱们就用VideoView来播放里面的RTSP的流,咱们以其中的一个链接来测试下好了:
rtsp://218.204.223.237:554/live/1/66251FC11353191F/e7ooqwcfbqjoo80j.sdp.
效果截图:
核心代码如下:
复制代码
在点击开始播放后,一般要等个10几秒中才开始播放的,直接的设置需要播放的RTSP的地址:setVideoURI(rtsp的地址)
VideoViewRtsp.rar (23.4
非常谢谢了,这篇文章给我很大的启示。为什么要使用
rtsp://218.204.223.237:554/live/1/66251FC11353191F/e7ooqwcfbqjoo80j.sdp
而不是rtsp://。。。。.mp4 为什么要后缀sdp呢?不能直接调用视频文件吗?
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++