Broadcasting video with Android - without writing to local files
One of the weaker points of the Android platform is the Media API. When compared to the J2ME API, one important feature is missing: the ability to record to a stream and to playback from a stream.
Why is this important? There are a number of use cases.
For recording:
- post-processing audio / video data before writing out to the file system
- broadcasting audio / video without writing out the data first into the file system, which also limits the broadcast to the available free space on the device.
For playback:
- pre-processing the audio / video data before playing
- streaming using protocols that are not supported by the built-in media player
In this blog entry we will show a method to broadcast video (and audio) from an Android phone to a network server, without writing to the file system.
There is one promising method in the MediaRecorder class setOutputFile(FileDescriptor).
We know that in Linux also network sockets have file descriptors. But how could we access the file descriptor of a regular java.net.Socket?
Luckily, ParcelFileDescriptor comes to the rescue, where we can use the fromSocket(Socket) static method to create a ParcelFileDescriptor instance from a Socket object. From this instance, we may now grab the badly needed FileDescriptor.
It all boils down to these few lines (in pseudocode):
String hostname = "your.host.name"; int port = 1234; Socket socket = new Socket(InetAddress.getByName(hostname), port); ParcelFileDescriptor pfd = ParcelFileDescriptor.fromSocket(socket); MediaRecorder recorder = new MediaRecorder(); // Additional MediaRecorder setup (output format ... etc.) omitted recorder.setOutputFile(pfd.getFileDescriptor()); recorder.prepare(); recorder.start();
Using this concept we created a small proof of concept application (together with an even simpler server), which is able to broadcast videos not limited in length by the available space on the SD Card.
There are a few gotchas, if you want to try this out yourself:
- The MediaRecorder records either in 3GPP or in MP4 format. This file format consists of atoms, where each atom starts with its size. There are different kinds of atoms in a file, mdat atoms store the actual raw frames of the encoded video and audio. In the Cupcake version Android starts writing out an mdat atom with the encoded frames, but it has to leave the size of the atom empty for obvious reasons. When writing to a seekable file descriptor, it can simply fill in the blanks after the recording, but of course socket file descriptors are not seekable. So the received stream will have to be fixed up after the recording is finished, or the raw video / audio frames have to be processed by the server.
- For some reason, the MediaRecorder also leaves the header of the file blank, which also has to be handled on the server.
- High latency connections will cause the video to be choppy. Obviously some buffering is necessary. One method is to use a local mini server on the phone which receives the stream, buffers it, and sends to the remote server as fast as the network allows it. However, if using native code is an option, we can simply create a pipe to receive the data from the MediaRecorder. We will show this method in a future blog entry.
Comments
very usefull
very usefull material,
thanks
Hi kisg, can you provide its
Hi kisg,
can you provide its full working source please?
really appreciate your help
Thanks for sharing great
Thanks for sharing great technique..
obviously very few people will know abt socket to fd.
thx
suds
Any tips for server side?
Hi
Can You please give some tips for the server side?
What are you using to capture/broadcast the stream?
thx
T
hi i have try to receive and
hi i have try to receive and play by MediaPlayer using this way
but i can't setup and play it.
could you give me some hint for that?
Socket socket = new Socket(serverAddr, PORT); //use tcp to receive
pfd = ParcelFileDescriptor.fromSocket(socket); //set pfd
MP = new MediaPlayer();
MP.setAudioStreamType(AudioManager.STREAM_MUSIC); //create MediaPlayer
MP.setDataSource(pfd.getFileDescriptor()); //set datasource by pfd
MP.prepare();
MP.start();
do i miss to set something?
thx
the same
Hi
i habe the save problem.
do u have got it out now?
thanks
Friend have you find any
Friend have you find any solution ?? plz let me know if you have.
Have u found its working.
Have u found its working, If yes then plz share it. I am get stuck at same point.
Thanks,
Parag Patel
Good post, but I have one more question...
Hi all,
really useful post... I would like to add one more element. I also need to stream raw data without any form of video encoding... is it possible to do???
Thanks and how about DatagramSocket?
Thanks for this wonderful post. I've been pulling my hair out on this for hours now. Is it possible to do a similar trick with a datagram socket?
Mark
Trying to get this to work
Hi kisg,
I've been trying to get this to work for a few weeks now... Do you have more information/documentation or code I could look at? It would be incredibly helpful if you could post them on the site or send them to me. Thanks.
Pat
Great
Great kisg,
please more details and snipped code, could be a success for all.
Thanks
Stefano
"For some reason, the
"For some reason, the MediaRecorder also leaves the header of the file blank, which also has to be handled on the server."
Any ideas how to handle this?
Is there a posibility to generate the header on the Server?
Awesome! Works flawlessly and
Awesome!
Works flawlessly and smooth!
I didn't even had to fix the stream, as the file generated on the server is working.
My previous attempt at this was to create 10s files and send them.
Profile of Leading Webcast Streaming Service Provider India
Virtual Studio is the pioneer http://www.vsworld.com/?loadSwf=swf/streaming.swf web cast streaming service provider company in India based out of New Delhi, having web cast for various corporates and multinationals, a number of events of international importance.
Repairing the Output
Just to help those having issues, the SDK seems to try to seek to insert the size values of the mdat atom, and also the moov header.
I set the encoder in this example to produce a THREE_GPP file.
In order to play the output THREE_GPP, you're going to need to first create the header in the first 28 bytes prior to the mdat atom (which should all be zeros).
00 00 00 18 66 74 79 70 33 67 70 34 00 00 03 00 33 67 70 34 33 67 70 36 00 02 F1 4D 6D
The 6D is the 'm' first byte in the mdat atom. The four bytes proceeding that need to be modified to include the integer value of the byte in your stream containing the output moov atom (Which should be output upon stopping the recording). As long as this header is correctly set, and the player can locate the moov atom- everything should play back correctly.
Also, the socket method here isn't very flexible- you can perform finer alterations of the packet data to a network (I'm attempting this at the moment for live streaming), by providing it with a local socket, and then connecting to that local socket and processing its output independently (In a thread for instance) for transmission over UDP, RTP, etc..
- Jason
Play incoming stream
Can we do same thing (user local socket) to receive incoming RTP stream which has H326 encodec data?
I have successfully send RTP stream, but when same way going to receive incoming RTP stream, MediaPlayer setDataSource function goes fail. giving me "offset error". I have looked Android MediaPlyer code, its giving error at below point.
status_t MediaPlayerService::Client::setDataSource(int fd, int64_t offset, int64_t length)
{
LOGV("setDataSource fd=%d, offset=%lld, length=%lld", fd, offset, length);
struct stat sb;
int ret = fstat(fd, &sb);
if (ret != 0) {
LOGE("fstat(%d) failed: %d, %s", fd, ret, strerror(errno));
return UNKNOWN_ERROR;
}
LOGV("st_dev = %llu", sb.st_dev);
LOGV("st_mode = %u", sb.st_mode);
LOGV("st_uid = %lu", sb.st_uid);
LOGV("st_gid = %lu", sb.st_gid);
LOGV("st_size = %llu", sb.st_size);
if (offset >= sb.st_size) {
LOGE("offset error");
::close(fd);
return UNKNOWN_ERROR;
}
Can any one help me to understand what I am doing wrong?
Thanks,
Parag Patel
It needs to understand the format.
Parag,
I'm trying to the same thing now- setting the data source to an fd with RTP data on it isn't going to work- since it has no ability to automatically detect the format type. There's no way to set this manually either :( MediaPlayer does support RTSP though, and it should be possible to send it the right SDP information to accomplish this.. I'm amazed nobody else has done this as far as I can tell...
- Jason Thomas.
sample code in sipdroid.org
you can get the sample code in VideoCamera.java in Sipdroid.org.
as for playback, it seems that android mediaplayer can not play rtp stream for lacking sdp information. if I am wrong, please correct me.
thanks.
Post new comment