Skip to content

Use MP4Muxer from jcodec on API level 16-18 and streamable #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ android {
}

dependencies {
compile 'org.jcodec:jcodec:0.1.9'
compile fileTree(dir: 'libs', include: ['*.jar'])
}
119 changes: 119 additions & 0 deletions lib/src/main/java/net/ypresto/androidtranscoder/muxer/AVCMP4Mux.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package net.ypresto.androidtranscoder.muxer;

import org.jcodec.codecs.h264.H264Utils;
import org.jcodec.codecs.h264.MappedH264ES;
import org.jcodec.codecs.h264.io.model.PictureParameterSet;
import org.jcodec.codecs.h264.io.model.SeqParameterSet;
import org.jcodec.codecs.h264.mp4.AvcCBox;
import org.jcodec.common.NIOUtils;
import org.jcodec.common.SeekableByteChannel;
import org.jcodec.common.model.Packet;
import org.jcodec.common.model.Size;
import org.jcodec.common.tools.MainUtils;
import org.jcodec.common.tools.MainUtils.Cmd;
import org.jcodec.containers.mp4.MP4Packet;
import org.jcodec.containers.mp4.TrackType;
import org.jcodec.containers.mp4.boxes.SampleEntry;
import org.jcodec.containers.mp4.muxer.FramesMP4MuxerTrack;
import org.jcodec.containers.mp4.muxer.MP4Muxer;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import static org.jcodec.codecs.h264.H264Utils.getPicHeightInMbs;
import static org.jcodec.common.NIOUtils.writableFileChannel;

/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* Sample code. Muxes H.264 ( MPEG4 AVC ) elementary stream into MP4 ( ISO
* 14496-1/14496-12/14496-14, Quicktime ) container
*
* @author Jay Codec
*
*/
// blob: 5284521e9747c0fc748b1879d78a680532926e0a
// modified: removed unused main(), change encoderName, package-nize static methods
class AVCMP4Mux {
private static AvcCBox avcC;

static void main(String[] args) throws Exception {
Cmd cmd = MainUtils.parseArguments(args);
if (cmd.argsLength() < 2) {
MainUtils.printHelp(new HashMap<String, String>() {
{
put("q", "Look for stream parameters only in the beginning of stream");
}
}, "in.264", "out.mp4");
System.exit(-1);
}

File in = new File(cmd.getArg(0));
File out = new File(cmd.getArg(1));

SeekableByteChannel file = writableFileChannel(out);
MP4Muxer muxer = new MP4Muxer(file);
FramesMP4MuxerTrack track = muxer.addTrack(TrackType.VIDEO, 25);

mux(track, in);

muxer.writeHeader();

file.close();
}

static void mux(FramesMP4MuxerTrack track, File f) throws IOException {
MappedH264ES es = new MappedH264ES(NIOUtils.map(f));

ArrayList<ByteBuffer> spsList = new ArrayList<ByteBuffer>();
ArrayList<ByteBuffer> ppsList = new ArrayList<ByteBuffer>();
Packet frame = null;
while ((frame = es.nextFrame()) != null) {
ByteBuffer data = NIOUtils.cloneBuffer(frame.getData());
H264Utils.wipePS(data, spsList, ppsList);
H264Utils.encodeMOVPacket(data);
MP4Packet pkt = new MP4Packet(new Packet(frame, data), frame.getPts(), 0);
System.out.println(pkt.getFrameNo());
track.addFrame(pkt);
}
addSampleEntry(track, es.getSps(), es.getPps());
}

static void addSampleEntry(FramesMP4MuxerTrack track, SeqParameterSet[] spss, PictureParameterSet[] ppss) {
SeqParameterSet sps = spss[0];
Size size = new Size((sps.pic_width_in_mbs_minus1 + 1) << 4, getPicHeightInMbs(sps) << 4);

SampleEntry se = MP4Muxer.videoSampleEntry("avc1", size, "AndroidTranscoder");

avcC = new AvcCBox(sps.profile_idc, 0, sps.level_idc, 4, write(spss), write(ppss));
se.add(avcC);
track.addSampleEntry(se);
}

static List<ByteBuffer> write(PictureParameterSet[] ppss) {
List<ByteBuffer> result = new ArrayList<ByteBuffer>();
for (PictureParameterSet pps : ppss) {
ByteBuffer buf = ByteBuffer.allocate(1024);
pps.write(buf);
buf.flip();
result.add(buf);
}
return result;
}

static List<ByteBuffer> write(SeqParameterSet[] spss) {
List<ByteBuffer> result = new ArrayList<ByteBuffer>();
for (SeqParameterSet sps : spss) {
ByteBuffer buf = ByteBuffer.allocate(1024);
sps.write(buf);
buf.flip();
result.add(buf);
}
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package net.ypresto.androidtranscoder.muxer;

import android.media.MediaCodec;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.os.Build;
import android.util.Log;

import java.io.IOException;
import java.nio.ByteBuffer;

public class AndroidMuxer implements Muxer {
private static final String TAG = "AndroidMuxer";
private MediaMuxer mMediaMuxer;

public AndroidMuxer(String path, int format) throws IOException {
mMediaMuxer = new MediaMuxer(path, format);
}

@Override
public void setOrientationHint(int degrees) {
mMediaMuxer.setOrientationHint(degrees);
}

@Override
public void setLocation(float latitude, float longitude) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
mMediaMuxer.setLocation(latitude, longitude);
} else {
Log.w(TAG, "setLocation: skipped because it is NOT supported by Android API level of this device.");
}
}

@Override
public void start() {
mMediaMuxer.start();
}

@Override
public void stop() {
mMediaMuxer.stop();
}

@Override
public int addTrack(MediaFormat format) {
return mMediaMuxer.addTrack(format);
}

@Override
public void writeSampleData(int trackIndex, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo) {
mMediaMuxer.writeSampleData(trackIndex, byteBuf, bufferInfo);
}

@Override
public void release() {
mMediaMuxer.release();
}
}
115 changes: 115 additions & 0 deletions lib/src/main/java/net/ypresto/androidtranscoder/muxer/JCodecMuxer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package net.ypresto.androidtranscoder.muxer;

import android.media.MediaCodec;
import android.media.MediaFormat;

import net.ypresto.androidtranscoder.utils.MediaFormatUtils;

import org.jcodec.codecs.h264.io.model.SeqParameterSet;
import org.jcodec.codecs.h264.mp4.AvcCBox;
import org.jcodec.common.FileChannelWrapper;
import org.jcodec.common.NIOUtils;
import org.jcodec.common.model.Size;
import org.jcodec.containers.mp4.MP4Packet;
import org.jcodec.containers.mp4.TrackType;
import org.jcodec.containers.mp4.boxes.SampleEntry;
import org.jcodec.containers.mp4.muxer.FramesMP4MuxerTrack;
import org.jcodec.containers.mp4.muxer.MP4Muxer;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class JCodecMuxer implements Muxer {
private final FileChannelWrapper mFileChannelWrapper;
private final MP4Muxer mMP4Muxer;
private final List<TrackContainer> mTrackContainers = new ArrayList<TrackContainer>();

public JCodecMuxer(String path, int bufferSize) throws IOException {
File out = new File(path);
mFileChannelWrapper = NIOUtils.writableFileChannel(out);
mMP4Muxer = new MP4Muxer(mFileChannelWrapper);
}

@Override
public void setOrientationHint(int degrees) {
}

@Override
public void setLocation(float latitude, float longitude) {

}

@Override
public void start() {

}

@Override
public void stop() {

}

@Override
public int addTrack(MediaFormat format) {
int index = mTrackContainers.size();
String mime = MediaFormatUtils.getMime(format);
if (mime.startsWith("video/")) {
mTrackContainers.add(addVideoTrack(format));
} else if (mime.startsWith("audio/")) {
mTrackContainers.add(addAudioTrack(format));
} else {
throw new UnsupportedOperationException("Unsupported track mime type: " + mime);
}
return index;
}

@Override
public void writeSampleData(int trackIndex, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo) {
byteBuf.position(bufferInfo.offset);
byteBuf.limit(bufferInfo.offset + bufferInfo.size);
TrackContainer trackContainer = mTrackContainers.get(trackIndex);
trackContainer.mTrack.addFrame(new MP4Packet(byteBuf, bufferInfo.presentationTimeUs, ));
}

@Override
public void release() {

}

private TrackContainer addVideoTrack(MediaFormat format) {
// Refer: AVCMP4Mux.java in jcodec

SeqParameterSet sps = SeqParameterSet.read(MediaFormatUtils.getSpsBuffer(format));
int timeScale = sps.vuiParams.time_scale;

FramesMP4MuxerTrack track = mMP4Muxer.addTrack(TrackType.VIDEO, timeScale);
Size size = new Size(MediaFormatUtils.getWidth(format), MediaFormatUtils.getHeight(format));
SampleEntry se = MP4Muxer.videoSampleEntry("avc1", size, "AndroidTranscoder");
AvcCBox avcC = new AvcCBox(sps.profile_idc, 0, sps.level_idc, 4,
Arrays.asList(MediaFormatUtils.getSpsBuffer(format)),
Arrays.asList(MediaFormatUtils.getPpsBuffer(format)));
se.add(avcC);
track.addSampleEntry(se);

return new TrackContainer(track, timeScale);
}

private FramesMP4MuxerTrack addAudioTrack(MediaFormat format) {
// TODO
return null;
}

private static class TrackContainer {
public final FramesMP4MuxerTrack mTrack;
public final int mTimeScale;

public TrackContainer(FramesMP4MuxerTrack track, int timeScale) {
mTrack = track;
mTimeScale = timeScale;
}
}
}
25 changes: 25 additions & 0 deletions lib/src/main/java/net/ypresto/androidtranscoder/muxer/Muxer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package net.ypresto.androidtranscoder.muxer;

import android.media.MediaCodec;
import android.media.MediaFormat;

import java.nio.ByteBuffer;

/**
* Abstraction interface to use non-Android muxer in same interface as {@link android.media.MediaMuxer}.
*/
public interface Muxer {
void setOrientationHint(int degrees);

void setLocation(float latitude, float longitude);

void start();

void stop();

int addTrack(MediaFormat format);

void writeSampleData(int trackIndex, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo);

void release();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package net.ypresto.androidtranscoder.muxer;

/**
* Created by yuya.tanaka on 2014/11/07.
*/
public class MuxerFactory {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package net.ypresto.androidtranscoder.utils;

import android.media.MediaFormat;

import java.nio.ByteBuffer;

public class MediaFormatUtils {

private MediaFormatUtils() {
}

public static String getMime(MediaFormat format) {
return format.getString(MediaFormat.KEY_MIME);
}

public static int getWidth(MediaFormat format) {
return format.getInteger(MediaFormat.KEY_WIDTH);
}

public static int getHeight(MediaFormat format) {
return format.getInteger(MediaFormat.KEY_HEIGHT);
}

public static ByteBuffer getSpsBuffer(MediaFormat format) {
return format.getByteBuffer("csd-0").duplicate();
}

public static ByteBuffer getPpsBuffer(MediaFormat format) {
return format.getByteBuffer("csd-1").duplicate();
}
}