FFmpegFrameRecorder 视频广播音频在 3G 网络中比视频帧更快

2024-05-07

我在用着FFmpegFrameRecorder用于视频广播。问题是音频比视频帧快。我正在使用以下代码,但无法生成完整的视频,音频视频时间戳存在问题。

Java代码:

import static com.googlecode.javacv.cpp.opencv_core.IPL_DEPTH_8U;

import java.io.IOException;
import java.nio.ShortBuffer;

import android.app.Activity;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.PowerManager;
import android.util.Log;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.LinearLayout;

import com.googlecode.javacv.FFmpegFrameRecorder;
import com.googlecode.javacv.cpp.opencv_core.IplImage;

public class MainActivity extends Activity implements OnClickListener {

    private final static String LOG_TAG = "MainActivity";

    private PowerManager.WakeLock mWakeLock;

    private String ffmpeg_link = "";

    private volatile FFmpegFrameRecorder recorder;
    boolean recording = false;
    long startTime = 0;

    private int sampleAudioRateInHz = 16000;
    private int imageWidth = 320;
    private int imageHeight = 240;
    private int frameRate = 24;

    private Thread audioThread;
    volatile boolean runAudioThread = true;
    private AudioRecord audioRecord;
    private AudioRecordRunnable audioRecordRunnable;

    private CameraView cameraView;
    private IplImage yuvIplimage = null;

    private Button recordButton;
    private LinearLayout mainLayout;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        setContentView(R.layout.activity_main);

        initLayout();
        initRecorder();
    }

    @Override
    protected void onResume() {
        super.onResume();

        if (mWakeLock == null) {
            PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
            mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK,
                    LOG_TAG);
            mWakeLock.acquire();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();

        if (mWakeLock != null) {
            mWakeLock.release();
            mWakeLock = null;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        recording = false;
    }

    private void initLayout() {

        mainLayout = (LinearLayout) this.findViewById(R.id.record_layout);

        recordButton = (Button) findViewById(R.id.recorder_control);
        recordButton.setText("Start");
        recordButton.setOnClickListener(this);

        cameraView = new CameraView(this);

        LinearLayout.LayoutParams layoutParam = new LinearLayout.LayoutParams(
                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        mainLayout.addView(cameraView, layoutParam);
        Log.v(LOG_TAG, "added cameraView to mainLayout");
    }

    private void initRecorder() {
        Log.w(LOG_TAG, "initRecorder");

        if (yuvIplimage == null) {
            // Recreated after frame size is set in surface change method
            yuvIplimage = IplImage.create(imageWidth, imageHeight,
                    IPL_DEPTH_8U, 2);
            // yuvIplimage = IplImage.create(imageWidth, imageHeight,
            // IPL_DEPTH_32S, 2);

            Log.v(LOG_TAG, "IplImage.create");
        }

        recorder = new FFmpegFrameRecorder(ffmpeg_link, imageWidth,
                imageHeight, 1);
        Log.v(LOG_TAG, "FFmpegFrameRecorder: " + ffmpeg_link + " imageWidth: "
                + imageWidth + " imageHeight " + imageHeight);

        recorder.setFormat("flv");
        Log.v(LOG_TAG, "recorder.setFormat(\"flv\")");

        recorder.setSampleRate(sampleAudioRateInHz);
        Log.v(LOG_TAG, "recorder.setSampleRate(sampleAudioRateInHz)");

        // re-set in the surface changed method as well
        recorder.setFrameRate(frameRate);
        Log.v(LOG_TAG, "recorder.setFrameRate(frameRate)");

        // Create audio recording thread
        audioRecordRunnable = new AudioRecordRunnable();
        audioThread = new Thread(audioRecordRunnable);
    }

    // Start the capture
    public void startRecording() {
        try {
            recorder.start();
            startTime = System.currentTimeMillis();
            recording = true;
            audioThread.start();
        } catch (FFmpegFrameRecorder.Exception e) {
            e.printStackTrace();
        }
    }

    public void stopRecording() {
        // This should stop the audio thread from running
        runAudioThread = false;

        if (recorder != null && recording) {
            recording = false;
            Log.v(LOG_TAG,
                    "Finishing recording, calling stop and release on recorder");
            try {
                recorder.stop();
                recorder.release();
            } catch (FFmpegFrameRecorder.Exception e) {
                e.printStackTrace();
            }
            recorder = null;
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        // Quit when back button is pushed
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            if (recording) {
                stopRecording();
            }
            finish();
            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public void onClick(View v) {
        if (!recording) {
            startRecording();
            Log.w(LOG_TAG, "Start Button Pushed");
            recordButton.setText("Stop");
        } else {
            stopRecording();
            Log.w(LOG_TAG, "Stop Button Pushed");
            recordButton.setText("Start");
        }
    }

    // ---------------------------------------------
    // audio thread, gets and encodes audio data
    // ---------------------------------------------
    class AudioRecordRunnable implements Runnable {

        @Override
        public void run() {
            // Set the thread priority
            android.os.Process
                    .setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);

            // Audio
            int bufferSize;
            short[] audioData;
            int bufferReadResult;

            bufferSize = AudioRecord.getMinBufferSize(sampleAudioRateInHz,
                    AudioFormat.CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_16BIT);
            audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
                    sampleAudioRateInHz,
                    AudioFormat.CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_16BIT, bufferSize);

            audioData = new short[bufferSize];

            Log.d(LOG_TAG, "audioRecord.startRecording()");
            audioRecord.startRecording();

            // Audio Capture/Encoding Loop
            while (runAudioThread) {
                // Read from audioRecord
                bufferReadResult = audioRecord.read(audioData, 0,
                        audioData.length);
                if (bufferReadResult > 0) {
                    // Log.v(LOG_TAG,"audioRecord bufferReadResult: " +
                    // bufferReadResult);

                    // Changes in this variable may not be picked up despite it
                    // being "volatile"
                    if (recording) {
                        try {
                            // Write to FFmpegFrameRecorder
                            recorder.record(ShortBuffer.wrap(audioData, 0,
                                    bufferReadResult));
                        } catch (FFmpegFrameRecorder.Exception e) {
                            Log.v(LOG_TAG, e.getMessage());
                            e.printStackTrace();
                        }
                    }
                }
            }
            Log.v(LOG_TAG, "AudioThread Finished");

            /* Capture/Encoding finished, release recorder */
            if (audioRecord != null) {
                audioRecord.stop();
                audioRecord.release();
                audioRecord = null;
                Log.v(LOG_TAG, "audioRecord released");
            }
        }
    }

    class CameraView extends SurfaceView implements SurfaceHolder.Callback,
            PreviewCallback {

        private boolean previewRunning = false;

        private SurfaceHolder holder;
        private Camera camera;

        private byte[] previewBuffer;

        long videoTimestamp = 0;

        Bitmap bitmap;
        Canvas canvas;

        public CameraView(Context _context) {
            super(_context);

            holder = this.getHolder();
            holder.addCallback(this);
            holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        }

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            camera = Camera.open();

            try {
                camera.setPreviewDisplay(holder);
                camera.setPreviewCallback(this);

                Camera.Parameters currentParams = camera.getParameters();
                Log.v(LOG_TAG,
                        "Preview Framerate: "
                                + currentParams.getPreviewFrameRate());
                Log.v(LOG_TAG,
                        "Preview imageWidth: "
                                + currentParams.getPreviewSize().width
                                + " imageHeight: "
                                + currentParams.getPreviewSize().height);

                // Use these values
                imageWidth = currentParams.getPreviewSize().width;
                imageHeight = currentParams.getPreviewSize().height;
                frameRate = currentParams.getPreviewFrameRate();

                bitmap = Bitmap.createBitmap(imageWidth, imageHeight,
                        Bitmap.Config.ALPHA_8);

                /*
                 * Log.v(LOG_TAG,"Creating previewBuffer size: " + imageWidth *
                 * imageHeight *
                 * ImageFormat.getBitsPerPixel(currentParams.getPreviewFormat
                 * ())/8); previewBuffer = new byte[imageWidth * imageHeight *
                 * ImageFormat
                 * .getBitsPerPixel(currentParams.getPreviewFormat())/8];
                 * camera.addCallbackBuffer(previewBuffer);
                 * camera.setPreviewCallbackWithBuffer(this);
                 */

                camera.startPreview();
                previewRunning = true;
            } catch (IOException e) {
                Log.v(LOG_TAG, e.getMessage());
                e.printStackTrace();
            }
        }

        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height) {
            Log.v(LOG_TAG, "Surface Changed: width " + width + " height: "
                    + height);

            // We would do this if we want to reset the camera parameters
            /*
             * if (!recording) { if (previewRunning){ camera.stopPreview(); }
             * 
             * try { //Camera.Parameters cameraParameters =
             * camera.getParameters(); //p.setPreviewSize(imageWidth,
             * imageHeight); //p.setPreviewFrameRate(frameRate);
             * //camera.setParameters(cameraParameters);
             * 
             * camera.setPreviewDisplay(holder); camera.startPreview();
             * previewRunning = true; } catch (IOException e) {
             * Log.e(LOG_TAG,e.getMessage()); e.printStackTrace(); } }
             */

            // Get the current parameters
            Camera.Parameters currentParams = camera.getParameters();
            Log.v(LOG_TAG,
                    "Preview Framerate: " + currentParams.getPreviewFrameRate());
            Log.v(LOG_TAG,
                    "Preview imageWidth: "
                            + currentParams.getPreviewSize().width
                            + " imageHeight: "
                            + currentParams.getPreviewSize().height);

            // Use these values
            imageWidth = currentParams.getPreviewSize().width;
            imageHeight = currentParams.getPreviewSize().height;
            frameRate = currentParams.getPreviewFrameRate();

            // Create the yuvIplimage if needed
            yuvIplimage = IplImage.create(imageWidth, imageHeight,
                    IPL_DEPTH_8U, 1);
            // yuvIplimage = IplImage.create(imageWidth, imageHeight,
            // IPL_DEPTH_32S, 2);
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            try {
                camera.setPreviewCallback(null);

                previewRunning = false;
                camera.release();

            } catch (RuntimeException e) {
                Log.v(LOG_TAG, e.getMessage());
                e.printStackTrace();
            }
        }

        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {

            if (yuvIplimage != null && recording) {
                videoTimestamp = 1000 * (System.currentTimeMillis() - startTime);

                // Put the camera preview frame right into the yuvIplimage
                // object
                System.out.println("value of data=============" + data);
                yuvIplimage.getByteBuffer().put(data);

                // FAQ about IplImage:
                // - For custom raw processing of data, getByteBuffer() returns
                // an NIO direct
                // buffer wrapped around the memory pointed by imageData, and
                // under Android we can
                // also use that Buffer with Bitmap.copyPixelsFromBuffer() and
                // copyPixelsToBuffer().
                // - To get a BufferedImage from an IplImage, we may call
                // getBufferedImage().
                // - The createFrom() factory method can construct an IplImage
                // from a BufferedImage.
                // - There are also a few copy*() methods for
                // BufferedImage<->IplImage data transfers.

                // Let's try it..
                // This works but only on transparency
                // Need to find the right Bitmap and IplImage matching types

                /*
                 * bitmap.copyPixelsFromBuffer(yuvIplimage.getByteBuffer());
                 * //bitmap.setPixel(10,10,Color.MAGENTA);
                 * 
                 * canvas = new Canvas(bitmap); Paint paint = new Paint();
                 * paint.setColor(Color.GREEN); float leftx = 20; float topy =
                 * 20; float rightx = 50; float bottomy = 100; RectF rectangle =
                 * new RectF(leftx,topy,rightx,bottomy);
                 * canvas.drawRect(rectangle, paint);
                 * 
                 * bitmap.copyPixelsToBuffer(yuvIplimage.getByteBuffer());
                 */
                // Log.v(LOG_TAG,"Writing Frame");

                try {

                    // Get the correct time
                    recorder.setTimestamp(videoTimestamp);

                    // Record the image into FFmpegFrameRecorder
                    recorder.record(yuvIplimage);

                } catch (FFmpegFrameRecorder.Exception e) {
                    Log.v(LOG_TAG, e.getMessage());
                    e.printStackTrace();
                }
            }
        }
    }
}

Manifest

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.javacv.stream.test2"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="15" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.javacv.stream.test2.MainActivity"
            android:label="@string/title_activity_main" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

这也是我们在 iOS 端遇到的一个已知问题。基本上,当音频继续播放时,视频数据包会被丢弃,一切都会陷入困境。低带宽下的一些硬件只是不能很好地发挥作用并且不同步。我不相信有一个可靠的解决方案,我们必须通过在顶部构建我们自己的音频/视频缓冲区并重新同步来破解它使用时间戳、帧大小和数据包计数。

恐怕我无法发布该代码(这不是我发布的),但如果您知道协议,那么重新创建应该不难。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FFmpegFrameRecorder 视频广播音频在 3G 网络中比视频帧更快 的相关文章

  • Android - 保存动态更改布局的状态

    我有一个布局 用户可以在其中添加按钮并将其放置在他们想要的位置 我想允许用户保存他们的布局 以便下次打开应用程序时加载它 有谁知道我是否可以将文件保存到 SD 卡上 或者 我可以使用某种layout getXml 方法并将其放入我的应用程序
  • 使用 Android 前台服务为 MediaPlayer 创建通知

    问题就在这里 我目前正在开发一个应用程序 该应用程序必须提供 A 广播播放器 来自 URL 的 AAC 直播 还有一个播客播放器 来自 URL 的 MP3 流 该应用程序必须能够在后台运行 Android 服务 并通过以下方式向用户公开持续
  • Android第一次动画不流畅

    我正在尝试一个动画将 imageView 从屏幕底部滑动到屏幕中心 但是当我第一次执行此动画时 它不平滑 但当第二次执行动画时 它是正常且平滑的 我几乎尝试了所有方法 但无法解决我的问题 这是我的动画文件
  • 在自定义对象中创建时粘性服务不会重新启动

    我有一个具有绑定服务的单例对象 我希望它重新启动 当我从启动器启动应用程序时 单例对象将初始化并绑定到这个现有的服务实例 以下是在单例中创建和绑定服务的代码 public class MyState private static MySta
  • 如何从 SQLite 获取记录总数

    我正在尝试从 Sqlite DB 获取行的总数 以下是我想要做的代码片段 我不知道我在这里做错了什么 public static int getTotalCount Context context Cursor c null try c g
  • 共同的偏好不断消失

    我正在使用共享首选项来存储我的应用程序的登录凭据 除了一个用户之外 一切正常 一段时间后 共享偏好似乎会以某种方式重置或清除 我已针对该用户调整了我的应用程序 使其不再清除他的共享偏好设置 这样我就可以确定这不是我的应用程序的错 但即使在这
  • 在 Android 中使用 DataOutputStream 在 POST 正文中发送特殊字符 (ë ä ï)

    我目前正在开发一个具有大量服务器端通信的 Android 应用程序 昨天 我收到一份错误报告 称用户无法发送 简单 特殊字符 例如 我搜索过但没有找到任何有用的东西 可能重复 没有答案 https stackoverflow com que
  • 如何查找 Android 设备中的所有文件并将它们放入列表中?

    我正在寻求帮助来列出 Android 外部存储设备中的所有文件 我想查找所有文件夹 包括主文件夹的子文件夹 有办法吗 我已经做了一个基本的工作 但我仍然没有得到想要的结果 这不起作用 这是我的代码 File files array file
  • 已经使用 AsyncTask doInBackground 但新数据未显示

    我使用 AsyncTask 创建一个聊天室来接收消息 因此它总是检查即将到来的消息并将其显示给客户端 但代码似乎无法按我希望的方式工作 在客户端只显示所有旧数据 新数据不显示 因为当我尝试从服务器发送消息时 新数据没有显示在客户端中 我对这
  • Android 原理图内容提供程序库配置?

    Jake Wharton 在最近的一次演讲中提到了这个库 它看起来是避免大量样板文件的好方法 所以我尝试了一下 但没有任何成功 https github com SimonVT schematic https github com Simo
  • 更新到材质 1.2.0 后,材质按钮上缺少圆角半径属性

    这是我的材质按钮代码
  • 请求位置更新参数

    这就是 requestLocationUpdates 的样子 我使用它的方式 requestLocationUpdates String provider long minTime float minDistance LocationLis
  • Android 版 Robotium - solo.searchText () 不起作用

    我在使用 Robotium 时遇到 searchText 函数问题 我正在寻找这个字符串
  • CookieManager.getInstance().removeAllCookie();不删除所有cookie

    我在应用程序的 onCreate 中调用 CookieManager getInstance removeAllCookie 我遇到了一个奇怪的问题 我看到 GET 请求中传递了意外的 cookie 值 事实上 cookie 值是一个非常非
  • 从 android 简单上传到 S3

    我在网上搜索了从 android 上传简单文件到 s3 的方法 但找不到任何有效的方法 我认为这是因为缺乏具体步骤 1 https mobile awsblog com post Tx1V588RKX5XPQB TransferManage
  • 下载后从谷歌照片库检索图像

    我正在发起从图库中获取照片的意图 当我在图库中使用 Nexus 谷歌照片应用程序时 一切正常 但如果图像不在手机上 在 Google Photos 在线服务上 它会为我下载 选择图像后 我将图像发送到另一个活动进行裁剪 但在下载的情况下 发
  • Android - 将 ImageView 保存到具有全分辨率图像的文件

    我将图像放入 ImageView 中 并实现了多点触控来调整 ImageView 中的图像大小和移动图像 现在我需要将调整大小的图像保存到图像文件中 我已经尝试过 getDrawingCache 但该图像具有 ImageView 的大小 我
  • 保护 APK 中的字符串

    我正在使用 Xamarin 的 Mono for Android 开发一个 Android 应用程序 我目前正在努力使用 Google Play API 添加应用内购买功能 为此 我需要从我的应用程序内向 Google 发送公共许可证密钥
  • android Accessibility-service 突然停止触发事件

    我有一个 AccessibilityService 工作正常 但由于开发过程中的某些原因它停止工作 我似乎找不到这个原因 请看一下我的代码并告诉我为什么它不起作用 public class MyServicee extends Access
  • 在 Android 中,如何将字符串从 Activity 传递到 Service?

    任何人都可以告诉如何将字符串或整数从活动传递到服务 我试图传递一个整数 setpossition 4 但它不需要 启动时总是需要 0 Service 我不知道为什么我不能通过使用 Service 实例从 Activity 进行操作 publ

随机推荐