android 录像本地网络传输保存成mp4文件

该博客介绍了如何通过MediaRecorder和LocalSocket在Android上实现本地网络录像传输,然后处理保存为可播放的MP4文件。通过修改MPEG4Writer源码以支持长时间录像,并详细阐述了处理过程,包括文件大小限制、MOOV Box调整和数据写入。此外,还提到了使用Pipe方式进行传输的替代方案。
摘要由CSDN通过智能技术生成

android 录像本地网络传输保存成mp4文件

说明

通过MediaRecorder加LocalSocket实现本地网络传输,再通过其它硬件接口(非网络接口)将mp4数据传送到PC端稍微处理一下
保存成播放器能正常播放的mp4文件,如果设备与服务器有网络连接可以直接跳过本记录使用android-libstreaming
通过libstreaming的例子1设置目标地址为本机127.0.0.1测试了一两次好像不行,放弃了。
本记录只适合录像时间比较固定,同时录像时间不能太长的应用

系统源码修改

源码路径frameworks/av/media/libstagefright/MPEG4Writer.cpp
1.函数MPEG4Writer::MPEG4Writer(int fd)

// Verify mFd is seekable
off64_t off = lseek64(mFd, 0, SEEK_SET);
if (off < 0) {
    ALOGE("cannot seek mFd: %s (%d)", strerror(errno), errno);
    //release();
}

注释掉其中的release,android5以下没有这个判断可以不用管

2.函数int64_t MPEG4Writer::estimateMoovBoxSize(int32_t bitRate)

static const int64_t MAX_MOOV_BOX_SIZE = (3000000 * 6LL / 8000) * 1800;//(180 * 3000000 * 6LL / 8000); modify 3minutes to 30 minutes

修改MAX_MOOV_BOX_SIZE最大为30分钟计算的大小
如果只改第1个地方,针对录像时间小于15秒左右最后保存的文件经过处理能正常播放,但大于15秒后,时间越长moovbox内容越多,最后回写的次数无法确定,不好处理。

应用软件

有些变量,可自行定义

public final static String SOCKET_ADDR = "com.gzease.camera";

1.mediarecorder设置,部分代码

  mMediaRecorder.setCamera(mCamera);
  CamcorderProfile mProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
  mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
  mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
  mMediaRecorder.setOutputFormat(mProfile.fileFormat);
  mMediaRecorder.setVideoEncodingBitRate(mProfile.videoBitRate);
  mMediaRecorder.setVideoEncoder(mProfile.videoCodec);
  mMediaRecorder.setVideoSize(mProfile.videoFrameWidth, mProfile.videoFrameHeight);
  mMediaRecorder.setVideoFrameRate(mProfile.videoFrameRate);
  mMediaRecorder.setAudioEncodingBitRate(mProfile.audioBitRate);
  mMediaRecorder.setAudioChannels(mProfile.audioChannels);
  mMediaRecorder.setAudioSamplingRate(mProfile.audioSampleRate);
  mMediaRecorder.setAudioEncoder(mProfile.audioCodec);
  mMediaRecorder.setMaxDuration(0);
  mMediaRecorder.setMaxFileSize(300*1024*1024);
  mMediaRecorder.setOrientationHint(90);
  mMediaRecorder.setPreviewDisplay(mSurfaceHolder != null ? mSurfaceHolder.getSurface() : null);
  if(DataBase.hasCamEnCodeStream) {
      DataBase.CamFileSize = 0;
      if(usbLocalSocket)
          mMediaRecorder.setOutputFile(localServer.getLocalSocket().getFileDescriptor());
      else
          mMediaRecorder.setOutputFile(mParcelWrite.getFileDescriptor());
  } else {
      File file = new File(DataBase.PATH_VIDEOFILE);
      if (file.exists())
          file.delete();
      try {
          fos = new FileOutputStream(DataBase.PATH_VIDEOFILE);
          //mMediaRecorder.setOutputFile(DataBase.PATH_VIDEOFILE);
          mMediaRecorder.setOutputFile(fos.getFD());
      } catch (FileNotFoundException e) {
          e.printStackTrace();
      } catch (IOException e) {
          e.printStackTrace();
      }
  }

  new LogTools(TAG, "startVideoRecording setOutputFile : " + DataBase.PATH_VIDEOFILE);
  try {
      mMediaRecorder.prepare();
      mMediaRecorder.start();
  } catch (IOException e) {
      e.printStackTrace();
      new LogTools().e(TAG, "startVideoRecording exception : " + e.getMessage());
      releaseVideoRecorder();
  }

一定要调用setMaxFileSize设置文件最大大小,不然系统mpeg4write中moovBox是按最小值处理,会影响最终处理流程

2.localserver

package com.gzease.CameraTools;

import android.net.LocalServerSocket;
import android.net.LocalSocket;

import com.gzease.tools.DataBase;
import com.gzease.tools.LogTools;

import java.io.IOException;
import java.io.InputStream;

public class LocalServer extends Thread{
    private final String TAG = "LocalServer";
    private final int debug = DataBase.D_LEVEL_OUT;

    private LocalServerSocket server;
    private boolean isRun = true;
    private boolean isConnect = false;
    private LocalSocket localSocket;
    @Override
    public void run() {
        super.run();
        try {
            server = new LocalServerSocket(DataBase.SOCKET_ADDR);
            new LogTools(TAG," accept start",debug);
            localSocket = server.accept();
            new LogTools(TAG," accept end",debug);
            isConnect = true;
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public LocalSocket getLocalSocket() {
        new LogTools(TAG," getLocalSocket isConnect:" + isConnect,debug);
        if(isConnect)
            return localSocket;
        else
            return null;
    }

    @Override
    public void interrupt() {
        super.interrupt();
        isRun = false;
    }
}

3.localclient

package com.gzease.CameraTools;

import android.net.LocalSocket;
import android.net.LocalSocketAddress;

import com.gzease.tools.DataBase;
import com.gzease.tools.LogTools;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;

public class LocalClient extends Thread{
    private static final String TAG = "LocalClient";
    private static final int debug = DataBase.D_LEVEL_OUT;
    private InputStream mInStream=null;
    private LocalSocket localSocket;
    private ReadThread readThread;
    private boolean hasWriteFile = false;
    private static String fileName = "/sdcard/test.mp4";
    private static String destFileName = "/sdcard/test1.mp4";
    @Override
    public void run() {
        super.run();
        try {
            Thread.sleep(1000);
            localSocket = new LocalSocket();
            new LogTools(TAG, "connect start",debug);
            localSocket.connect(new LocalSocketAddress(DataBase.SOCKET_ADDR));
            new LogTools(TAG, "connect end",debug);
            mInStream = localSocket.getInputStream();
            if(mInStream != null) {
                readThread = new ReadThread();
                readThread.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    public LocalSocket getLocalSocket() {
        if(mInStream != null)
            return  localSocket;
        else
            return null;
    }

    @Override
    public void interrupt() {
        super.interrupt();
        if(readThread != null)
            readThread.interrupt();
    }

    class ReadThread extends Thread {
        private boolean isRun = true;
        @Override
        public void run() {
            super.run();
            try {
                while (isRun) {
                    //byte[] buffer = new byte[1024 * 1024];
                    byte[] buffer = new byte[5 * 1024];
                    int size = mInStream.read(buffer);
                    if(size > 0) {
                        if(hasWriteFile) {
                            if(DataBase.CamFileSize == 0) {
                                File file = new File(fileName);
                                if(file.exists())
                                    file.delete();
                            }
                            toFile(buffer,size);
                        } else
                            sendtoPc(buffer,size);
                        DataBase.CamFileSize += size;
                        new LogTools(TAG," DataBase.CamFileSize:" + DataBase.CamFileSize,debug);
                    } else {
                        this.interrupt();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void interrupt() {
            super.interrupt();
            isRun = false;
        }
    }

    private void sendtoPc(byte[] buf,int size) {
    }

    private void toFile(byte[] buf,int size) {
        try {
            // 打开一个随机访问文件流,按读写方式
            RandomAccessFile randomFile = new RandomAccessFile(fileName, "rw");
            // 文件长度,字节数
            long fileLength = randomFile.length();
            // 将写文件指针移到文件尾。
            randomFile.seek(fileLength);
            randomFile.write(buf,0,size);
            randomFile.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void DealFile() {
        RandomAccessFile srcfile = null;
        RandomAccessFile destfile = null;
        long starttime,endtime;
        starttime = System.currentTimeMillis();
        new LogTools(TAG,"start time :" + starttime,debug);
        try {
            File file = new File(fileName);
            File file1 = new File(destFileName);
            if(file1.exists())
                file1.delete();
            srcfile = new RandomAccessFile(fileName, "r");
            destfile = new RandomAccessFile(destFileName, "rw");
            new LogTools(TAG,"filelen :" + file.length(),debug);
            srcfile.seek(24);//rewrite head
            destfile.writeInt(srcfile.readInt());
            srcfile.seek(4);
            byte[] dat = new byte[20];//writeFtypBox ftyp+mp42+0000+isom+mp42
            srcfile.read(dat);
            destfile.write(dat);
            long moovStart = searchEndStr(fileName,"moov");
            new LogTools(TAG,"moovStart:" + moovStart,debug);
            srcfile.seek(moovStart - 4);
            int moovSize = srcfile.readInt();
            destfile.writeInt(moovSize);
            dat = new byte[moovSize - 4];
            srcfile.read(dat);
            destfile.write(dat);
            srcfile.seek(file.length() - 8);
            int freeSize = srcfile.readInt();
            new LogTools(TAG,"freeSize:" + Integer.toHexString(freeSize),debug);
            destfile.writeInt(freeSize);//4
            destfile.write("free".getBytes());//4
            dat = new byte[freeSize - 4 - 4];
            destfile.write(dat);
            srcfile.seek(moovStart - 8);//media data size
            int mediaDataSize = srcfile.readInt();
            destfile.writeInt(mediaDataSize);//write media data size
            srcfile.seek(40);
            mediaDataSize -= 4;//start from mdat
            while (true) {
                byte[] mdat = new byte[100*1024];
                srcfile.read(mdat);
                if(mediaDataSize > mdat.length) {
                    mediaDataSize -= mdat.length;
                    destfile.write(mdat);
                } else {
                    destfile.write(mdat,0,mediaDataSize);
                    mediaDataSize = 0;
                }
                new LogTools(TAG,"media size1:" + mediaDataSize,debug);
                if(mediaDataSize <= 0)
                    break;
            }
            srcfile.close();
            destfile.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private static long searchEndStr(String fileName,String info) {
        long index = 0;
        int searchlen = 2*1024;
        File file = new File(fileName);
        long starttime,endtime;
        starttime = System.currentTimeMillis();
        new LogTools(TAG,"searchEndStr start time :" + starttime,debug);
        try {
            RandomAccessFile srcfile = new RandomAccessFile(fileName, "r");
            for(int i = 1;;i++) {
                long start = file.length() - searchlen * i;
                if(start < 0)
                    break;
                srcfile.seek(file.length() - searchlen * i);
                byte[] dat = new byte[searchlen];
                srcfile.read(dat);
                String datStr = new String(dat,"ASCII");
                index = datStr.lastIndexOf(info);
                if(index > 0) {
                    index = start + index;
                    break;
                }
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        endtime = System.currentTimeMillis();
        new LogTools(TAG,"searchEndStr end time :" + endtime + " cal:" + (endtime - starttime),debug);
        return index;
    }
}

录像完成后使用DealFile处理文件,得到最终能正常播放的test1.mp4,PC上的处理也是参考这个函数.
这种方式,最终的mp4文件会比一般的要大1M多左右,因为设置了最大文件大小导致free区数据比较大.但最终处理相对简单。

4.也可以通过Pipe方式
初始化

try {
    mParcelFileDescriptors = ParcelFileDescriptor.createPipe();
    mParcelRead = new ParcelFileDescriptor(mParcelFileDescriptors[0]);
    mParcelWrite = new ParcelFileDescriptor(mParcelFileDescriptors[1]);
    new LogTools(TAG, "mParcelWrite:" + mParcelWrite + " mParcelRead:" + mParcelRead, debug);
    if (mParcelRead != null) {
        PipeReadThread pipeReadThread = new PipeReadThread(mParcelRead);
        pipeReadThread.start();
    }
} catch (IOException e) {
    e.printStackTrace();
}

PipeReadThread.java

package com.gzease.CameraTools;

import android.os.ParcelFileDescriptor;

import com.gzease.tools.DataBase;
import com.gzease.tools.LogTools;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;

public class PipeReadThread extends Thread{
    private final String TAG = "PipeReadThread";
    private final int debug = DataBase.D_LEVEL_OUT;
    private boolean isRun = true;
    private InputStream is = null;
    private boolean hasWriteFile = true;
    private String fileName = "/sdcard/test.mp4";
    public PipeReadThread(ParcelFileDescriptor fileDescriptor) {
        is = new ParcelFileDescriptor.AutoCloseInputStream(fileDescriptor);
        new LogTools(TAG," is: " + is,debug);
    }

    @Override
    public void run() {
        super.run();
        if(is == null)
            return;
        while (isRun) {
            try {
                byte[] buffer = new byte[30 * 1024];
                int size = is.read(buffer);
                if(size > 0) {
                    if(hasWriteFile) {
                        if(DataBase.CamFileSize == 0) {
                            File file = new File(fileName);
                            if(file.exists())
                                file.delete();
                        }
                        toFile(buffer,size);
                    }
                    DataBase.CamFileSize += size;
                    new LogTools(TAG," size: " + size + " dat:" + DataBase.bytesToHexString(buffer,size)
                            + " DataBase.CamFileSize:" + DataBase.CamFileSize,debug);
                } else {
                    this.interrupt();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void interrupt() {
        super.interrupt();
        isRun = false;
    }

    private void toFile(byte[] buf,int size) {
        try {
            // 打开一个随机访问文件流,按读写方式
            RandomAccessFile randomFile = new RandomAccessFile(fileName, "rw");
            // 文件长度,字节数
            long fileLength = randomFile.length();
            // 将写文件指针移到文件尾。
            randomFile.seek(fileLength);
            randomFile.write(buf,0,size);
            randomFile.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

===================================
作者:hclydao
http://blog.csdn.net/hclydao
版权没有,但是转载请保留此段声明

======================================

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值