阿里云服务器免费领卷啦。

捡代码论坛-最全的游戏源码下载技术网站!

 找回密码
 立 即 注 册

QQ登录

只需一步,快速开始

搜索
关于源码区的附件失效或欺骗帖, 处理办法
查看: 3337|回复: 3

Android使用FFMpeg实现推送视频直播流到服务器

[复制链接]

4209

主题

210

回帖

12万

积分

管理员

管理员

Rank: 9Rank: 9Rank: 9

积分
126353
QQ
发表于 2016-3-22 12:01:20 | 显示全部楼层 |阅读模式
背景
在过去的2015年中,视频直播页的新宠无疑是户外直播。随着4G网络的普及和覆盖率的提升,主播可以在户外通过手机进行直播。而观众也愿意为这种可以足不出户而观天下事的服务买单。基于这样的背景,本文主要实现在Android设备上采集视频并推流到服务器。
概览如下图所示,在安卓上采集并推流主要应用到两个类。首先是安卓Api自带的Camera,实现从摄像头采集图像。然后是Javacv 中的FFMpegFrameRecorder类实现对Camera采集到的帧编码并推流。 TB2zdpskXXXXXbTXXXXXXXXXXXX_!!754328530.jpg
关键步骤与代码
下面结合上面的流程图给出视频采集的关键步骤。 首先是Camera类的初始化。
  1. // 初始化Camera设备
  2. cameraDevice = Camera.open();
  3.      Log.i(LOG_TAG, "cameara open");
  4.      cameraView = new CameraView(this, cameraDevice);
复制代码
上面的CameraView类是我们实现的负责预览视频采集和将采集到的帧写入FFMpegFrameRecorder的类。具体代码如下:

  1. class CameraView extends SurfaceView implements SurfaceHolder.Callback, PreviewCallback {

  2.     private SurfaceHolder mHolder;
  3.     private Camera mCamera;

  4.     public CameraView(Context context, Camera camera) {
  5.         super(context);
  6.         Log.w("camera", "camera view");
  7.         mCamera = camera;
  8.         mHolder = getHolder();
  9.         //设置SurfaceView 的SurfaceHolder的回调函数
  10.         mHolder.addCallback(CameraView.this);
  11.         mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
  12.         //设置Camera预览的回调函数
  13.         mCamera.setPreviewCallback(CameraView.this);
  14.     }

  15.     @Override
  16.     public void surfaceCreated(SurfaceHolder holder) {
  17.         try {
  18.             stopPreview();
  19.             mCamera.setPreviewDisplay(holder);
  20.         } catch (IOException exception) {
  21.             mCamera.release();
  22.             mCamera = null;
  23.         }
  24.     }

  25.     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
  26.         stopPreview();

  27.         Camera.Parameters camParams = mCamera.getParameters();
  28.         List<Camera.Size> sizes = camParams.getSupportedPreviewSizes();
  29.         // Sort the list in ascending order
  30.         Collections.sort(sizes, new Comparator<Camera.Size>() {

  31.             public int compare(final Camera.Size a, final Camera.Size b) {
  32.                 return a.width * a.height - b.width * b.height;
  33.             }
  34.         });

  35.         // Pick the first preview size that is equal or bigger, or pick the last (biggest) option if we cannot
  36.         // reach the initial settings of imageWidth/imageHeight.
  37.         for (int i = 0; i < sizes.size(); i++) {
  38.             if ((sizes.get(i).width >= imageWidth && sizes.get(i).height >= imageHeight) || i == sizes.size() - 1) {
  39.                 imageWidth = sizes.get(i).width;
  40.                 imageHeight = sizes.get(i).height;
  41.                 Log.v(LOG_TAG, "Changed to supported resolution: " + imageWidth + "x" + imageHeight);
  42.                 break;
  43.             }
  44.         }
  45.         camParams.setPreviewSize(imageWidth, imageHeight);

  46.         Log.v(LOG_TAG, "Setting imageWidth: " + imageWidth + " imageHeight: " + imageHeight + " frameRate: " + frameRate);

  47.         camParams.setPreviewFrameRate(frameRate);
  48.         Log.v(LOG_TAG, "Preview Framerate: " + camParams.getPreviewFrameRate());

  49.         mCamera.setParameters(camParams);

  50.         // Set the holder (which might have changed) again
  51.         try {
  52.             mCamera.setPreviewDisplay(holder);
  53.             mCamera.setPreviewCallback(CameraView.this);
  54.             startPreview();
  55.         } catch (Exception e) {
  56.             Log.e(LOG_TAG, "Could not set preview display in surfaceChanged");
  57.         }
  58.     }

  59.     @Override
  60.     public void surfaceDestroyed(SurfaceHolder holder) {
  61.         try {
  62.             mHolder.addCallback(null);
  63.             mCamera.setPreviewCallback(null);
  64.         } catch (RuntimeException e) {
  65.             // The camera has probably just been released, ignore.
  66.         }
  67.     }

  68.     public void startPreview() {
  69.         if (!isPreviewOn && mCamera != null) {
  70.             isPreviewOn = true;
  71.             mCamera.startPreview();
  72.         }
  73.     }

  74.     public void stopPreview() {
  75.         if (isPreviewOn && mCamera != null) {
  76.             isPreviewOn = false;
  77.             mCamera.stopPreview();
  78.         }
  79.     }

  80.     @Override
  81.     public void onPreviewFrame(byte[] data, Camera camera) {
  82.         if (audioRecord == null || audioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING) {
  83.             startTime = System.currentTimeMillis();
  84.             return;
  85.         }
  86.         //如果是录播,则把该帧先存在内存中
  87.         if (RECORD_LENGTH > 0) {
  88.             int i = imagesIndex++ % images.length;
  89.             yuvImage = images[i];
  90.             timestamps[i] = 1000 * (System.currentTimeMillis() - startTime);
  91.         }
  92.         if (yuvImage != null && recording) {
  93.             ((ByteBuffer) yuvImage.image[0].position(0)).put(data);
  94.                         //如果是直播则直接写入到FFmpegFrameRecorder中
  95.             if (RECORD_LENGTH <= 0) try {
  96.                 Log.v(LOG_TAG, "Writing Frame");
  97.                 long t = 1000 * (System.currentTimeMillis() - startTime);
  98.                 if (t > recorder.getTimestamp()) {
  99.                     recorder.setTimestamp(t);
  100.                 }
  101.                 recorder.record(yuvImage);
  102.             } catch (FFmpegFrameRecorder.Exception e) {
  103.                 Log.v(LOG_TAG, e.getMessage());
  104.                 e.printStackTrace();
  105.             }
  106.         }
  107.     }
  108. }
复制代码
初始化FFmpegFrameRecorder类
  1. recorder = new FFmpegFrameRecorder(ffmpeg_link, imageWidth, imageHeight, 1);
  2.     //设置视频编码  28 指代h.264
  3.     recorder.setVideoCodec(28);
  4.     recorder.setFormat("flv");
  5.     //设置采样频率
  6.     recorder.setSampleRate(sampleAudioRateInHz);
  7.     // 设置帧率,即每秒的图像数
  8.     recorder.setFrameRate(frameRate);
  9.     //音频采集线程
  10.         audioRecordRunnable = new AudioRecordRunnable();
  11.     audioThread = new Thread(audioRecordRunnable);
  12.     runAudioThread = true;
复制代码
其中的AudioRecordRunnable是我们自己实现的音频采集线程,代码如下


  1. class AudioRecordRunnable implements Runnable {

  2.     @Override
  3.     public void run() {
  4.         android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);

  5.         // Audio
  6.         int bufferSize;
  7.         ShortBuffer audioData;
  8.         int bufferReadResult;

  9.         bufferSize = AudioRecord.getMinBufferSize(sampleAudioRateInHz,
  10.                 AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
  11.         audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleAudioRateInHz,
  12.                 AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
  13.                 //如果是录播,则需要录播长度的缓存
  14.         if (RECORD_LENGTH > 0) {
  15.             samplesIndex = 0;
  16.             samples = new ShortBuffer[RECORD_LENGTH * sampleAudioRateInHz * 2 / bufferSize + 1];
  17.             for (int i = 0; i < samples.length; i++) {
  18.                 samples[i] = ShortBuffer.allocate(bufferSize);
  19.             }
  20.         } else {
  21.         //直播只需要相当于一帧的音频的数据缓存
  22.             audioData = ShortBuffer.allocate(bufferSize);
  23.         }

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

  26.         /* ffmpeg_audio encoding loop */
  27.         while (runAudioThread) {
  28.             if (RECORD_LENGTH > 0) {
  29.                 audioData = samples[samplesIndex++ % samples.length];
  30.                 audioData.position(0).limit(0);
  31.             }
  32.             //Log.v(LOG_TAG,"recording? " + recording);
  33.             bufferReadResult = audioRecord.read(audioData.array(), 0, audioData.capacity());
  34.             audioData.limit(bufferReadResult);
  35.             if (bufferReadResult > 0) {
  36.                 Log.v(LOG_TAG, "bufferReadResult: " + bufferReadResult);
  37.                 // If "recording" isn't true when start this thread, it never get's set according to this if statement...!!!
  38.                 // Why?  Good question...
  39.                 if (recording) {
  40.                                //如果是直播,则直接调用recordSamples 将音频写入Recorder
  41.                     if (RECORD_LENGTH <= 0) try {
  42.                         recorder.recordSamples(audioData);
  43.                         //Log.v(LOG_TAG,"recording " + 1024*i + " to " + 1024*i+1024);
  44.                     } catch (FFmpegFrameRecorder.Exception e) {
  45.                         Log.v(LOG_TAG, e.getMessage());
  46.                         e.printStackTrace();
  47.                     }
  48.                 }
  49.             }
  50.         }
  51.         Log.v(LOG_TAG, "AudioThread Finished, release audioRecord");

  52.         /* encoding finish, release recorder */
  53.         if (audioRecord != null) {
  54.             audioRecord.stop();
  55.             audioRecord.release();
  56.             audioRecord = null;
  57.             Log.v(LOG_TAG, "audioRecord released");
  58.         }
  59.     }
  60. }
复制代码
接下来是开始直播和停止直播的方法
  1. //开始直播
  2. public void startRecording() {

  3.     initRecorder();

  4.     try {
  5.         recorder.start();
  6.         startTime = System.currentTimeMillis();
  7.         recording = true;
  8.         audioThread.start();

  9.     } catch (FFmpegFrameRecorder.Exception e) {
  10.         e.printStackTrace();
  11.     }
  12. }

  13. public void stopRecording() {
  14.         //停止音频线程
  15.     runAudioThread = false;
  16.     try {
  17.         audioThread.join();
  18.     } catch (InterruptedException e) {
  19.         e.printStackTrace();
  20.     }
  21.     audioRecordRunnable = null;
  22.     audioThread = null;

  23.     if (recorder != null && recording) {
  24.     //如果是录播,则将缓存中的帧加上时间戳后写入
  25.         if (RECORD_LENGTH > 0) {
  26.             Log.v(LOG_TAG, "Writing frames");
  27.             try {
  28.                 int firstIndex = imagesIndex % samples.length;
  29.                 int lastIndex = (imagesIndex - 1) % images.length;
  30.                 if (imagesIndex <= images.length) {
  31.                     firstIndex = 0;
  32.                     lastIndex = imagesIndex - 1;
  33.                 }
  34.                 if ((startTime = timestamps[lastIndex] - RECORD_LENGTH * 1000000L) < 0) {
  35.                     startTime = 0;
  36.                 }
  37.                 if (lastIndex < firstIndex) {
  38.                     lastIndex += images.length;
  39.                 }
  40.                 for (int i = firstIndex; i <= lastIndex; i++) {
  41.                     long t = timestamps[i % timestamps.length] - startTime;
  42.                     if (t >= 0) {
  43.                         if (t > recorder.getTimestamp()) {
  44.                             recorder.setTimestamp(t);
  45.                         }
  46.                         recorder.record(images[i % images.length]);
  47.                     }
  48.                 }

  49.                 firstIndex = samplesIndex % samples.length;
  50.                 lastIndex = (samplesIndex - 1) % samples.length;
  51.                 if (samplesIndex <= samples.length) {
  52.                     firstIndex = 0;
  53.                     lastIndex = samplesIndex - 1;
  54.                 }
  55.                 if (lastIndex < firstIndex) {
  56.                     lastIndex += samples.length;
  57.                 }
  58.                 for (int i = firstIndex; i <= lastIndex; i++) {
  59.                     recorder.recordSamples(samples[i % samples.length]);
  60.                 }
  61.             } catch (FFmpegFrameRecorder.Exception e) {
  62.                 Log.v(LOG_TAG, e.getMessage());
  63.                 e.printStackTrace();
  64.             }
  65.         }

  66.         recording = false;
  67.         Log.v(LOG_TAG, "Finishing recording, calling stop and release on recorder");
  68.         try {
  69.             recorder.stop();
  70.             recorder.release();
  71.         } catch (FFmpegFrameRecorder.Exception e) {
  72.             e.printStackTrace();
  73.         }
  74.         recorder = null;

  75.     }
  76. }
复制代码


原文链接
以上即为关键的步骤和代码,下面给出完整项目地址

回复可见:捡代码论坛整理提供:


游客,如果您要查看本帖隐藏内容请回复










捡代码论坛-最全的游戏源码下载技术网站! - 论坛版权郑重声明:
1、本主题所有言论和图片纯属会员个人意见,与本论坛立场无关
2、本站所有主题由该帖子作者发表,该帖子作者与捡代码论坛-最全的游戏源码下载技术网站!享有帖子相关版权
3、捡代码论坛版权,详细了解请点击。
4、本站所有内容均由互联网收集整理、网友上传,并且以计算机技术研究交流为目的,仅供大家参考、学习,不存在任何商业目的与商业用途。
5、若您需要商业运营或用于其他商业活动,请您购买正版授权并合法使用。 我们不承担任何技术及版权问题,且不对任何资源负法律责任。
6、如无法链接失效或侵犯版权,请给我们来信:jiandaima@foxmail.com

回复

使用道具 举报

4209

主题

210

回帖

12万

积分

管理员

管理员

Rank: 9Rank: 9Rank: 9

积分
126353
QQ
 楼主| 发表于 2016-5-17 17:58:07 | 显示全部楼层
登录可见评论
回复

使用道具 举报

0

主题

3

回帖

18

积分

新手上路

Rank: 1

积分
18
发表于 2016-11-3 12:29:53 | 显示全部楼层
登录可见评论
回复

使用道具 举报

0

主题

31

回帖

146

积分

注册会员

Rank: 2

积分
146
发表于 2017-7-14 01:56:34 | 显示全部楼层
登录可见评论
回复

使用道具 举报

*滑块验证:
您需要登录后才可以回帖 登录 | 立 即 注 册

本版积分规则

技术支持
在线咨询
QQ咨询
3351529868

QQ|手机版|小黑屋|捡代码论坛-专业源码分享下载 ( 陕ICP备15015195号-1|网站地图

GMT+8, 2024-6-18 21:14

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表