#1107 closed enhancement (worksforme)
support some container formats for video
Reported by: | alas | Owned by: | Antoine Martin |
---|---|---|---|
Priority: | major | Milestone: | 1.0 |
Component: | html5 | Version: | trunk |
Keywords: | Cc: |
Description
It would be nice to be able to use a container format, such as webm, for h264 encodings (specifically) for use in processing in html5 video elements. (The native video processing in the browsers don't process naked h264 packets very well.)
Attachments (7)
Change History (24)
comment:1 Changed 5 years ago by
comment:2 Changed 5 years ago by
Owner: | changed from Josh to Antoine Martin |
---|---|
Status: | new → assigned |
Summary: | Would like to be able to use a container format for h264 packets → support some container formats for video |
The two main contenders that I can see:
both available on Fedora and Debian, but centos doesn't have either of them...
libmkv is meant to be more portable (plain C instead of C++) - its header files look more sane, but its API is file based and we only want in-memory streaming.. converting that to buffer access might be doable, if a bit risky.
webm could be used for vp8/vp9 only.. and is not supported by IE or Safari.
Changed 5 years ago by
Attachment: | save-video-to-file.patch added |
---|
makes it possible to save the raw video stream to a file on the server
comment:3 Changed 5 years ago by
mpeg4 is the only container supported by all browsers: http://www.w3schools.com/html/html5_video.asp. Removal of Ogg Vorbis and Theora from HTML5: an outrageous disaster - monopolies lying through their teeth, and we let them get away with it, again.
Unsurprisingly, the only open-source project that does what we need is ffmpeg: MOV/MP4/ISMV (Smooth Streaming) muxer: A fragmented file consists of a number of fragments, where packets and metadata about these packets are stored together.
Downside is that we don't use ffmpeg for h264, which will make it harder to use the container code... or we have to make a new encoder using ffmpeg. This would need new ffmpeg builds with support for those muxers (that part is no big deal).
So, I am tempted to start with ogg or webm because that should be easier.
comment:4 Changed 5 years ago by
Webm: so many copies of similar file-oriented code everywhere:
- https://chromium.googlesource.com/webm/libvpx/+/master/webmenc.h
- http://www.webmproject.org/docs/webm-sdk/example_vpxenc.html
- https://github.com/webmproject/libwebm/blob/master/mkvwriter.cpp
ffmpeg links for muxing examples:
- http://ffmpeg.org/doxygen/trunk/doc_2examples_2decoding_encoding_8c-example.html
- http://ffmpeg.org/doxygen/trunk/doc_2examples_2muxing_8c-example.html
- http://stackoverflow.com/questions/11537830/ffmpeg-api-h264-encoded-video-does-not-play-on-all-platforms
- http://stackoverflow.com/questions/17118340/remuxing-mp4-on-the-fly-with-ffmpeg-api
- http://stackoverflow.com/questions/14052457/how-can-i-mux-h264-stream-into-mp4-file-via-libavformat
- VLC links:
- https://fossies.org/linux/vlc/modules/mux/mp4.c
- https://fossies.org/dox/vlc-2.2.2/mux_2ogg_8c_source.html
According to the matroska faq, ogg is better suited for streaming and lossy transports (ie: UDP).
More information on the MP4 container format:
comment:5 Changed 5 years ago by
Using the save-to-file feature merged in r12144, then adding the mpeg4 container using ffmpeg:
ffmpeg -i in.h264 -vcodec copy -f mp4 -movflags faststart out.mp4
I can see that the mpeg4 header is about 0x660 bytes long, and is followed by the raw h264 frames as "mdat".
It looks like what we want is ffmpeg's frag_custom mode: mp4 options: Allow the caller to manually choose when to cut fragments, by calling av_write_frame(ctx, NULL) to write a fragment with the packets written so far so that we can fragment on each frame.
comment:6 Changed 5 years ago by
Milestone: | 0.17 → 0.18 |
---|
Changed 5 years ago by
Attachment: | video-container-templates.patch added |
---|
stubs for 3 new video container plugins
comment:7 Changed 5 years ago by
Interesting gstreamer discussion: https://lists.freedesktop.org/archives/gstreamer-devel/2016-April/057989.html, quote: mp4 is not a streamable format in the sense of being able to be played as it is generated.
comment:8 Changed 5 years ago by
The patch above gives us AVFrame and AVPacket from our imagewrapper class.
Maybe this will be added even without the muxer.
The next step is the muxer, using libavformat muxing.
Looks like we need AVIOContext and maybe we need to buffer it in memory to support seeking: None of the function pointers in AVIOContext should be called directly, they should only be set by the client application when implementing custom I/O. Normally these are set to the function pointers specified in avio_alloc_context()
Calls to avformat_alloc_output_context2,avformat_write_header and av_write_frame should give us the stream, as long as we manage to configure all the magic flags required for fragmented mpeg4 streams..
comment:9 Changed 5 years ago by
We may want to tie this with colour profiles (#1155) since the profile can be specified in the stream and this may be used by the hardware decoders - even if browsers like chromium do not support icc profiles (Support color management transform of ICC profile tagged images to device profile on Linux)
Changed 5 years ago by
Attachment: | enc_ffmpeg-v7.patch added |
---|
comment:10 Changed 5 years ago by
r12955 adds the enc_ffmpeg codec (see commit message for some important limitations). It does support b-frames (#800).
New AVCodec API was very useful here, Bitstream Filtering should help with the muxing part.
This is pretty close to what we want: FFMPEG I/O output buffer
Changed 5 years ago by
Attachment: | mpeg4-muxer-v2.patch added |
---|
generates mpeg4 container with h264 embedded... to file for now
Changed 5 years ago by
Attachment: | mpeg4-muxer-v9.patch added |
---|
muxing work if you use pointers as unsigned long, gets mangled otherwise..
comment:12 Changed 4 years ago by
merged in r14427 (big), still needs work in the html5 client as the video is lagging in Firefox and not playing at all in chrome..
comment:13 Changed 4 years ago by
Resolution: | → worksforme |
---|---|
Status: | assigned → closed |
Works well enough but can only be tested with the html5 client...
The streams can be saved to file with XPRA_SAVE_TO_FILE=basename
.
The resulting files can be inspected with https://www.bento4.com/'s mp4dump or ffmpeg's ffprobe and looks correct in both cases. It can be played with any decent media player too.
Closing as this will get tested as part of #1341.
comment:14 Changed 4 years ago by
comment:15 Changed 3 years ago by
comment:16 Changed 3 years ago by
Got this reply about movflags / faststart:
I have succeeded in moving the moov atom.
Firstly, I set the movflags option when writing the header:
AVDictionary *options = NULL; av_dict_set(&options, "movflags", "faststart", 0); CHECK(avformat_write_header(oc, &options), "Error occurred when writing media header");
Secondly, because I have a custom AVIOContext on my format context, I needed to overwrite the io_open and io_close callbacks on my format context so we don't try to open a non-existent file when writing the trailer.
AVIOContext* ioContext = avio_alloc_context(buffer, BUFFER_SIZE, 1, streamPtr, &Read, &Write, &Seek); formatContext->pb = ioContext; formatContext->flags = AVFMT_FLAG_CUSTOM_IO; formatContext->io_open = &Open; formatContext->io_close = &Close;
My last step was disabling buffering on the AVIOContent I created in the Open callback. This was necessary due to my wrapping the buffer in my original AVIOcontext. The buffer would be invalid. Note that the below code is c++/cli (.net mixed mode).
static int Open(struct AVFormatContext *s, AVIOContext **pb, const char *url, int flags, AVDictionary **options) { GCHandle streamHandle = GCHandle::FromIntPtr(IntPtr(s->pb->opaque)); Stream ^stream = (Stream ^)streamHandle.Target; StreamWrapper ^wrapper = gcnew StreamWrapper(stream); //TODO: check allocations (may be out of memory) unsigned char * buffer = (unsigned char *)av_malloc( BUFFER_SIZE ); streamHandle = GCHandle::Alloc(wrapper); void* streamPtr = GCHandle::ToIntPtr(streamHandle).ToPointer(); *(pb) = avio_alloc_context(buffer, BUFFER_SIZE , 1, streamPtr, &Read, &Write, &Seek); (*pb)->direct = 1; return 0; } static void Close(struct AVFormatContext *s, AVIOContext *pb) { GCHandle streamHandle = GCHandle::FromIntPtr(IntPtr(pb->opaque)); streamHandle.Free(); av_free(pb->buffer); }
If you take a look movenc.c where the mov data is being placed, you'll see where it tries to reopen the file:
/* Shift the data: the AVIO context of the output can only be used for * writing, so we re-open the same output, but for reading. It also avoids * a read/seek/write/seek back and forth. */ avio_flush(s->pb); ret = s->io_open(s, &read_pb, s->filename, AVIO_FLAG_READ, NULL);
It so happens that my AVIOContext is read/write (backed by a .net stream). My StreamWrapper
class simply handles the
read/seek/write/seek that this code tries to avoid. I went the wrote of a proxy because I did *not* want to load another stream into memory.
Implementing this buffer seeking has now been moved to #1869.
comment:17 Changed 3 months ago by
this ticket has been moved to: https://github.com/Xpra-org/xpra/issues/1107
As a quick update... would it be possible to use webm & mp4 also, in the event that h264 doesn't work with webm?