- android - 多次调用 OnPrimaryClipChangedListener
- android - 无法更新 RecyclerView 中的 TextView 字段
- android.database.CursorIndexOutOfBoundsException : Index 0 requested, 光标大小为 0
- android - 使用 AppCompat 时,我们是否需要明确指定其 UI 组件(Spinner、EditText)颜色
我正在尝试使用 Android 4.3 中提供的 AudioRecord
、MediaCodec
和 MediaMuxer
录制音频和视频但是,有时音频编码器线程停止并且不再编码。结果是一个损坏的 mp4 文件,因为复用器没有收到任何编码的音频帧。在我的 Samsung Galaxy Note 3 上,它的工作率为 99%,但在我的 Sony Xperia Z1 上,编码线程总是卡住。我真的不知道是什么原因,也许有人可以帮助我优化我的代码:
录音机.java
package com.cmdd.horicam;
import java.nio.ByteBuffer;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.media.MediaRecorder;
import android.os.Looper;
import android.util.Log;
public class AudioRecorder implements Runnable {
public static final String TAG = "AudioRecorder";
public static final boolean VERBOSE = false;
public MovieMuxerAudioHandler mAudioHandler;
// audio format settings
public static final String MIME_TYPE_AUDIO = "audio/mp4a-latm";
public static final int SAMPLE_RATE = 44100;
public static final int CHANNEL_COUNT = 1;
public static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
public static final int BIT_RATE_AUDIO = 128000;
public static final int SAMPLES_PER_FRAME = 1024; // AAC
public static final int FRAMES_PER_BUFFER = 24;
public static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
public static final int AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;
public static final int MSG_START_RECORDING = 0;
public static final int MSG_STOP_RECORDING = 1;
public static final int MSG_QUIT = 2;
private MediaCodec mAudioEncoder;
private int iBufferSize;
int iReadResult = 0;
private boolean bIsRecording = false;
private static final int TIMEOUT_USEC = 10000;
private MovieMuxer mMovieMuxer;
private MediaFormat mAudioFormat;
private volatile AudioRecorderHandler mHandler;
private Object mReadyFence = new Object(); // guards ready/running
private boolean mReady;
private boolean mRunning;
public AudioRecorder(MovieMuxer mMovieMuxer){
this.mMovieMuxer = mMovieMuxer;
}
/**
* Recorder thread entry point. Establishes Looper/Handler and waits for messages.
* <p>
* @see java.lang.Thread#run()
*/
@Override
public void run() {
// Establish a Looper for this thread, and define a Handler for it.
Looper.prepare();
synchronized (mReadyFence) {
mHandler = new AudioRecorderHandler(this);
mReady = true;
mReadyFence.notify();
}
Looper.loop();
if(VERBOSE)Log.d(TAG, "audio recorder exiting thread");
synchronized (mReadyFence) {
mReady = mRunning = false;
mHandler = null;
}
}
public void prepareEncoder(){
// prepare audio format
mAudioFormat = MediaFormat.createAudioFormat(MIME_TYPE_AUDIO, SAMPLE_RATE, CHANNEL_COUNT);
mAudioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
mAudioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 16384);
mAudioFormat.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE_AUDIO);
mAudioEncoder = MediaCodec.createEncoderByType(MIME_TYPE_AUDIO);
mAudioEncoder.configure(mAudioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mAudioEncoder.start();
new Thread(new AudioEncoderTask(), "AudioEncoderTask").start();
}
public void prepareRecorder() {
int iMinBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT);
bIsRecording = false;
iBufferSize = SAMPLES_PER_FRAME * FRAMES_PER_BUFFER;
// Ensure buffer is adequately sized for the AudioRecord
// object to initialize
if (iBufferSize < iMinBufferSize)
iBufferSize = ((iMinBufferSize / SAMPLES_PER_FRAME) + 1) * SAMPLES_PER_FRAME * 2;
AudioRecord mAudioRecorder;
mAudioRecorder = new AudioRecord(
AUDIO_SOURCE, // source
SAMPLE_RATE, // sample rate, hz
CHANNEL_CONFIG, // channels
AUDIO_FORMAT, // audio format
iBufferSize); // buffer size (bytes)
mAudioRecorder.startRecording();
new Thread(new AudioRecorderTask(mAudioRecorder), "AudioRecorderTask").start();
}
/**
* Tells the audio recorder to start recording. (Call from non-encoder thread.)
* <p>
* Creates a new thread, which will create an encoder using the provided configuration.
* <p>
* Returns after the recorder thread has started and is ready to accept Messages. The
* encoder may not yet be fully configured.
*/
public void startRecording() {
if(VERBOSE)Log.d(TAG, "audio recorder: startRecording()");
synchronized (mReadyFence) {
if (mRunning) {
Log.w(TAG, "audio recorder thread already running");
return;
}
mRunning = true;
new Thread(this, "AudioRecorder").start();
while (!mReady) {
try {
mReadyFence.wait();
} catch (InterruptedException ie) {
// ignore
}
}
}
mHandler.sendMessage(mHandler.obtainMessage(MSG_START_RECORDING));
}
public void handleStartRecording(){
if(VERBOSE)Log.d(TAG, "handleStartRecording");
prepareEncoder();
prepareRecorder();
bIsRecording = true;
}
/**
* Tells the video recorder to stop recording. (Call from non-encoder thread.)
* <p>
* Returns immediately; the encoder/muxer may not yet be finished creating the movie.
* <p>
*/
public void stopRecording() {
if(mHandler != null){
mHandler.sendMessage(mHandler.obtainMessage(MSG_STOP_RECORDING));
mHandler.sendMessage(mHandler.obtainMessage(MSG_QUIT));
}
}
/**
* Handles a request to stop encoding.
*/
public void handleStopRecording() {
if(VERBOSE)Log.d(TAG, "handleStopRecording");
bIsRecording = false;
}
public String getCurrentAudioFormat(){
if(this.mAudioFormat == null)
return "null";
else
return this.mAudioFormat.toString();
}
private class AudioRecorderTask implements Runnable {
AudioRecord mAudioRecorder;
ByteBuffer[] inputBuffers;
ByteBuffer inputBuffer;
public AudioRecorderTask(AudioRecord recorder){
this.mAudioRecorder = recorder;
}
@Override
public void run() {
if(VERBOSE)Log.i(TAG, "AudioRecorder started recording");
long audioPresentationTimeNs;
byte[] mTempBuffer = new byte[SAMPLES_PER_FRAME];
while (bIsRecording) {
audioPresentationTimeNs = System.nanoTime();
iReadResult = mAudioRecorder.read(mTempBuffer, 0, SAMPLES_PER_FRAME);
if(iReadResult == AudioRecord.ERROR_BAD_VALUE || iReadResult == AudioRecord.ERROR_INVALID_OPERATION)
Log.e(TAG, "audio buffer read error");
// send current frame data to encoder
try {
if(inputBuffers == null)
inputBuffers = mAudioEncoder.getInputBuffers();
int inputBufferIndex = mAudioEncoder.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(mTempBuffer);
//recycleInputBuffer(mTempBuffer);
if(VERBOSE)Log.d(TAG, "sending frame to audio encoder");
mAudioEncoder.queueInputBuffer(inputBufferIndex, 0, mTempBuffer.length, audioPresentationTimeNs / 1000, 0);
}
} catch (Throwable t) {
Log.e(TAG, "sendFrameToAudioEncoder exception");
t.printStackTrace();
}
}
// finished recording -> send it to the encoder
audioPresentationTimeNs = System.nanoTime();
iReadResult = mAudioRecorder.read(mTempBuffer, 0, SAMPLES_PER_FRAME);
if (iReadResult == AudioRecord.ERROR_BAD_VALUE
|| iReadResult == AudioRecord.ERROR_INVALID_OPERATION)
Log.e(TAG, "audio buffer read error");
// send current frame data to encoder
try {
int inputBufferIndex = mAudioEncoder.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(mTempBuffer);
if(VERBOSE)Log.d(TAG, "sending EOS to audio encoder");
mAudioEncoder.queueInputBuffer(inputBufferIndex, 0, mTempBuffer.length, audioPresentationTimeNs / 1000, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
}
} catch (Throwable t) {
Log.e(TAG, "sendFrameToAudioEncoder exception");
t.printStackTrace();
}
//if (mAudioRecorder != null) {
// mAudioRecorder.release();
// mAudioRecorder = null;
// if(VERBOSE)Log.i(TAG, "stopped");
//}
}
}
private class AudioEncoderTask implements Runnable {
private boolean bAudioEncoderFinished;
private int iAudioTrackIndex;
private MediaCodec.BufferInfo mAudioBufferInfo;
@Override
public void run(){
if(VERBOSE)Log.i(TAG, "AudioEncoder started encoding");
bAudioEncoderFinished = false;
ByteBuffer[] encoderOutputBuffers = mAudioEncoder.getOutputBuffers();
ByteBuffer encodedData;
mAudioBufferInfo = new MediaCodec.BufferInfo();
while(!bAudioEncoderFinished){
int encoderStatus = mAudioEncoder.dequeueOutputBuffer(mAudioBufferInfo, TIMEOUT_USEC);
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
// no output available yet
if (VERBOSE) Log.d(TAG + "_encoder", "no output available, spinning to await EOS");
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
// not expected for an encoder
encoderOutputBuffers = mAudioEncoder.getOutputBuffers();
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat newFormat = mAudioEncoder.getOutputFormat();
if(VERBOSE)Log.d(TAG, "received output format: " + newFormat);
// should happen before receiving buffers, and should only happen once
iAudioTrackIndex = mMovieMuxer.addTrack(newFormat);
} else if (encoderStatus < 0) {
Log.w(TAG + "_encoder", "unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
// let's ignore it
} else {
if(mMovieMuxer.muxerStarted()){
encodedData = encoderOutputBuffers[encoderStatus];
if (encodedData == null) {
throw new RuntimeException("encoderOutputBuffer " + encoderStatus + " was null");
}
if ((mAudioBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
// The codec config data was pulled out and fed to the muxer when we got
// the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it.
if (VERBOSE) Log.d(TAG + "_encoder", "ignoring BUFFER_FLAG_CODEC_CONFIG");
mAudioBufferInfo.size = 0;
}
if (mAudioBufferInfo.size != 0) {
// adjust the ByteBuffer values to match BufferInfo (not needed?)
encodedData.position(mAudioBufferInfo.offset);
encodedData.limit(mAudioBufferInfo.offset + mAudioBufferInfo.size);
mMovieMuxer.writeSampleData(iAudioTrackIndex, encodedData, mAudioBufferInfo);
if (VERBOSE) {
Log.d(TAG + "_encoder", "sent " + mAudioBufferInfo.size + " bytes (audio) to muxer, ts=" + mAudioBufferInfo.presentationTimeUs);
}
}
mAudioEncoder.releaseOutputBuffer(encoderStatus, false);
if ((mAudioBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
// reached EOS
if(VERBOSE)Log.i(TAG + "_encoder", "audio encoder finished");
bAudioEncoderFinished = true;
// tell the muxer that we are finished
mAudioHandler.onAudioEncodingFinished();
break;
}
}
}
}
}
}
}
感谢您的帮助。
最佳答案
当您从音频记录中请求数据时:
iReadResult = mAudioRecorder.read(mTempBuffer, 0, SAMPLES_PER_FRAME);
您可以获得多个帧,然后 mediacodec 中的 pts 预测器将根据帧数和压缩帧持续时间生成适当的输出 pts。然后你可以在编码器 dequeueoutputbuffer 之后打印这些时间戳,并看到实际值将是 !0。但是随后您将在输入时再次为编码器提供 0 点,它将重置内部预测。这一切都会导致非单调的 pts 生成,并且 muxer 可能已经为此提示,请检查 adb 日志。对我来说,这已经发生了,我必须在喂入编码器之前手动设置采样时间。
mTempBuffer.setSampleTime(calc_pts_for_that_frame);
至少您可以检查这是否是您面临的问题,如果是,则可以通过计算适当的时间戳轻松解决。
关于android - 在 Android 上使用 MediaMuxer 录制音频和视频,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22867832/
我最近在/ drawable中添加了一些.gifs,以便可以将它们与按钮一起使用。这个工作正常(没有错误)。现在,当我重建/运行我的应用程序时,出现以下错误: Error: Gradle: Execu
Android 中有返回内部存储数据路径的方法吗? 我有 2 部 Android 智能手机(Samsung s2 和 s7 edge),我在其中安装了一个应用程序。我想使用位于这条路径中的 sqlit
这个问题在这里已经有了答案: What's the difference between "?android:" and "@android:" in an android layout xml f
我只想知道 android 开发手机、android 普通手机和 android root 手机之间的实际区别。 我们不能从实体店或除 android marketplace 以外的其他地方购买开发手
自Gradle更新以来,我正在努力使这个项目达到标准。这是一个团队项目,它使用的是android-apt插件。我已经进行了必要的语法更改(编译->实现和apt->注释处理器),但是编译器仍在告诉我存在
我是android和kotlin的新手,所以请原谅要解决的一个非常简单的问题! 我已经使用导航体系结构组件创建了一个基本应用程序,使用了底部的导航栏和三个导航选项。每个导航选项都指向一个专用片段,该片
我目前正在使用 Facebook official SDK for Android . 我现在正在使用高级示例应用程序,但我不知道如何让它获取应用程序墙/流/状态而不是登录的用户。 这可能吗?在那种情
我在下载文件时遇到问题, 我可以在模拟器中下载文件,但无法在手机上使用。我已经定义了上网和写入 SD 卡的权限。 我在服务器上有一个 doc 文件,如果用户单击下载。它下载文件。这在模拟器中工作正常但
这个问题在这里已经有了答案: What is the difference between gravity and layout_gravity in Android? (22 个答案) 关闭 9
任何人都可以告诉我什么是 android 缓存和应用程序缓存,因为当我们谈论缓存清理应用程序时,它的作用是,缓存清理概念是清理应用程序缓存还是像内存管理一样主存储、RAM、缓存是不同的并且据我所知,缓
假设应用程序 Foo 和 Eggs 在同一台 Android 设备上。任一应用程序都可以获取设备上所有应用程序的列表。一个应用程序是否有可能知道另一个应用程序是否已经运行以及运行了多长时间? 最佳答案
我有点困惑,我只看到了从 android 到 pc 或者从 android 到 pc 的例子。我需要制作一个从两部手机 (android) 连接的 android 应用程序进行视频聊天。我在想,我知道
用于使用 Android 以编程方式锁定屏幕。我从 Stackoverflow 之前关于此的问题中得到了一些好主意,并且我做得很好,但是当我运行该代码时,没有异常和错误。而且,屏幕没有锁定。请在这段代
文档说: android:layout_alignParentStart If true, makes the start edge of this view match the start edge
我不知道这两个属性和高度之间的区别。 以一个TextView为例,如果我将它的layout_width设置为wrap_content,并将它的width设置为50 dip,会发生什么情况? 最佳答案
这两个属性有什么关系?如果我有 android:noHistory="true",那么有 android:finishOnTaskLaunch="true" 有什么意义吗? 最佳答案 假设您的应用中有
我是新手,正在尝试理解以下 XML 代码: 查看 developer.android.com 上的文档,它说“starStyle”是 R.attr 中的常量, public static final
在下面的代码中,为什么当我设置时单选按钮的外观会发生变化 android:layout_width="fill_parent" 和 android:width="fill_parent" 我说的是
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 9
假设我有一个函数 fun myFunction(name:String, email:String){},当我调用这个函数时 myFunction('Ali', 'ali@test.com ') 如何
我是一名优秀的程序员,十分优秀!