http://blog.csdn.NET/a992036795/article/details/54583571
前言
本文介绍的是使用Android摄像头、麦克风采集的音、视频进行编码。然后通过librtmp推送到流媒体服务器上的功能。
我所使用的环境:Android Studio 2.2.3 、NDK13。
流程
使用到的Api
- 音视频采集用到的api有:Camera、AudioRecord
- 编码用的是系统提供的API:MediaCodec (硬编码)
- 推送使用的开源库:librtmp。
代码
ManActivity.Java
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback2 {
static final int NAL_SLICE = 1;
static final int NAL_SLICE_DPA = 2;
static final int NAL_SLICE_DPB = 3;
static final int NAL_SLICE_DPC = 4;
static final int NAL_SLICE_IDR = 5;
static final int NAL_SEI = 6;
static final int NAL_SPS = 7;
static final int NAL_PPS = 8;
static final int NAL_AUD = 9;
static final int NAL_FILLER = 12;
private static final String TAG = "MainActivity";
public static final String url = "rtmp://192.168.155.1:1935/live/test";
private Button btnToggle;
private SurfaceView mSurfaceView;
private SurfaceHolder mSurfaceHolder;
private Camera mCamera;
private Camera.Size previewSize;
private long presentationTimeUs;
MediaCodec vencoder;
private Thread recordThread;
private boolean aLoop;
private AudioRecord mAudioRecord;
private byte[] aBuffer;
private MediaCodec aencoder;
private int aSampleRate;
private int aChanelCount;
private int colorFormat;
private MediaCodec.BufferInfo aBufferInfo = new MediaCodec.BufferInfo();
private MediaCodec.BufferInfo vBufferInfo = new MediaCodec.BufferInfo();
private boolean isPublished;
private RtmpPublisher mRtmpPublisher = new RtmpPublisher();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
btnToggle = (Button) findViewById(R.id.btn_toggle);
mSurfaceView = (SurfaceView) findViewById(R.id.surface_view);
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.addCallback(this);
btnToggle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
switchPublish();
}
});
}
private void switchPublish() {
if (isPublished) {
stop();
} else {
start();
}
btnToggle.setText(isPublished ? "停止" : "开始");
}
private void start() {
//初始化
mRtmpPublisher.init(url, previewSize.width, previewSize.height, 5);
isPublished = true;
initAudioDevice();
try {
vencoder = initVideoEncoder();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("video encoder init fail");
}
try {
aencoder = initAudioEncoder();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("audio encoder init fail");
}
//开启录音
aLoop = true;
recordThread = new Thread(fetchAudioRunnable());
presentationTimeUs = new Date().getTime() * 1000;
mAudioRecord.startRecording();
recordThread.start();
if (aencoder != null) {
aencoder.start();
}
if (vencoder != null) {
vencoder.start();
}
}
private void stop() {
isPublished = false;
mRtmpPublisher.stop();
aLoop = false;
if (recordThread != null) {
recordThread.interrupt();
}
mAudioRecord.stop();
mAudioRecord.release();
vencoder.stop();
vencoder.release();
aencoder.stop();
aencoder.release();
}
private Runnable fetchAudioRunnable() {
return new Runnable() {
@Override
public void run() {
fetchPcmFromDevice();
}
};
}
private void fetchPcmFromDevice() {
Log.d(TAG, "录音线程开始");
while (aLoop && mAudioRecord != null && !Thread.interrupted()) {
int size = mAudioRecord.read(aBuffer, 0, aBuffer.length);
if (size < 0) {
Log.i(TAG, "audio ignore ,no data to read");
break;
}
if (aLoop) {
byte[] audio = new byte[size];
System.arraycopy(aBuffer, 0, audio, 0, size);
onGetPcmFrame(audio);
}
}
}
private void initAudioDevice() {
int[] sampleRates = {44100, 22050, 16000, 11025};
for (int sampleRate :
sampleRates) {
//编码制式
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
// stereo 立体声,
int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
int buffsize = 2 * AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
mAudioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, sampleRate, channelConfig,
audioFormat, buffsize);
if (mAudioRecord.getState() != AudioRecord.STATE_INITIALIZED) {
continue;
}
aSampleRate = sampleRate;
aChanelCount = channelConfig == AudioFormat.CHANNEL_CONFIGURATION_STEREO ? 2 : 1;
aBuffer = new byte[Math.min(4096, buffsize)];
}
}
private MediaCodec initAudioEncoder() throws IOException {
MediaCodec aencoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
MediaFormat format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC,
aSampleRate, aChanelCount);
format.setInteger(KEY_MAX_INPUT_SIZE, 0);
format.setInteger(KEY_BIT_RATE, 1000 * 30);
aencoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
return aencoder;
}
private MediaCodec initVideoEncoder() throws IOException {
// 初始化
MediaCodecInfo mediaCodecInfo = getMediaCodecInfoByType(MediaFormat.MIMETYPE_VIDEO_AVC);
colorFormat = getColorFormat(mediaCodecInfo);
MediaCodec vencoder = MediaCodec.createByCodecName(mediaCodecInfo.getName());
MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC,
previewSize.width, previewSize.height);
format.setInteger(KEY_MAX_INPUT_SIZE, 0);
format.setInteger(KEY_BIT_RATE, 700 * 1000);
format.setInteger(KEY_COLOR_FORMAT, colorFormat);
format.setInteger(KEY_FRAME_RATE, 20);
format.setInteger(KEY_I_FRAME_INTERVAL, 5);
vencoder.configure(format, null, null, CONFIGURE_FLAG_ENCODE);
return vencoder;
}
public static MediaCodecInfo getMediaCodecInfoByType(String mimeType) {
for (int i = 0; i < MediaCodecList.getCodecCount(); i++) {
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
if (!codecInfo.isEncoder()) {
continue;
}
String[] types = codecInfo.getSupportedTypes();
for (int j = 0; j < types.length; j++) {
if (types[j].equalsIgnoreCase(mimeType)) {
return codecInfo;
}
}
}
return null;
}
public static int getColorFormat(MediaCodecInfo mediaCodecInfo) {
int matchedForamt = 0;
MediaCodecInfo.CodecCapabilities codecCapabilities =
mediaCodecInfo.getCapabilitiesForType(MediaFormat.MIMETYPE_VIDEO_AVC);
for (int i = 0; i < codecCapabilities.colorFormats.length; i++) {
int format = codecCapabilities.colorFormats[i];
if (format >= codecCapabilities.COLOR_FormatYUV420Planar &&
format <= codecCapabilities.COLOR_FormatYUV420PackedSemiPlanar
) {
if (format >= matchedForamt) {
matchedForamt = format;
}
}
}
return matchedForamt;
}
private void initCamera() {
openCamera();
setCameraParameters();
setCameraDisplayOrientation(this, Camera.CameraInfo.CAMERA_FACING_BACK, mCamera);
try {
mCamera.setPreviewDisplay(mSurfaceHolder);
} catch (IOException e) {
e.printStackTrace();
}
mCamera.setPreviewCallbackWithBuffer(getPreviewCallback());
mCamera.addCallbackBuffer(new byte[calculateFrameSize(ImageFormat.NV21)]);
mCamera.startPreview();
}
private int calculateFrameSize(int format) {
return previewSize.width * previewSize.height * ImageFormat.getBitsPerPixel(format) / 8;
}
public static void setCameraDisplayOrientation(Activity activity,
int cameraId, android.hardware.Camera camera) {
android.hardware.Camera.CameraInfo info =
new android.hardware.Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(cameraId, info);
int rotation = activity.getWindowManager().getDefaultDisplay()
.getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0:
degrees = 0;
break;
case Surface.ROTATION_90:
degrees = 90;
break;
case Surface.ROTATION_180:
degrees = 180;
break;
case Surface.ROTATION_270:
degrees = 270;
break;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - degrees + 360) % 360;
}
camera.setDisplayOrientation(result);
}
private void setCameraParameters() {
Camera.Parameters parameters = mCamera.getParameters();
List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
for (Camera.Size size : supportedPreviewSizes
) {
if (size.width >= 320 && size.width <= 720) {
previewSize = size;
Log.d(TAG, String.format("find preview size width=%d,height=%d", previewSize.width,
previewSize.height));
break;
}
}
int[] destRange = {25 * 1000, 45 * 1000};
List<int[]> supportedPreviewFpsRange = parameters.getSupportedPreviewFpsRange();
for (int[] range : supportedPreviewFpsRange
) {
if (range[PREVIEW_FPS_MIN_INDEX] <= 45 * 1000 && range[PREVIEW_FPS_MAX_INDEX] >= 25 * 1000) {
destRange = range;
Log.d(TAG, String.format("find fps range :%s", Arrays.toString(destRange)));
break;
}
}
parameters.setPreviewSize(previewSize.width, previewSize.height);
parameters.setPreviewFpsRange(destRange[PREVIEW_FPS_MIN_INDEX],
destRange[PREVIEW_FPS_MAX_INDEX]);
parameters.setFocusMode(FOCUS_MODE_AUTO);
parameters.setPreviewFormat(ImageFormat.NV21);
parameters.setRotation(onOrientationChanged(0));
mCamera.setParameters(parameters);
}
public int onOrientationChanged(int orientation) {
android.hardware.Camera.CameraInfo info =
new android.hardware.Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, info);
orientation = (orientation + 45) / 90 * 90;
int rotation = 0;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
rotation = (info.orientation - orientation + 360) % 360;
} else { // back-facing camera
rotation = (info.orientation + orientation) % 360;
}
return rotation;
}
private void openCamera() {
if (mCamera == null) {
try {
mCamera = Camera.open();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "打开摄像头失败", Toast.LENGTH_SHORT).show();
try {
Thread.sleep(3000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
throw new RuntimeException("打开摄像头失败", e);
}
}
}
@Override
protected void onResume() {
super.onResume();
initCamera();
}
@Override
protected void onPause() {
super.onPause();
if (mCamera != null) {
mCamera.setPreviewCallbackWithBuffer(null);
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
}
@Override
public void surfaceRedrawNeeded(SurfaceHolder holder) {
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
initCamera();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
public Camera.PreviewCallback getPreviewCallback() {
return new Camera.PreviewCallback() {
byte[] dstByte = new byte[calculateFrameSize(ImageFormat.NV21)];
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
if (data != null) {
if (isPublished) {
// data 是Nv21
if (colorFormat == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar) {
Yuv420Util.Nv21ToYuv420SP(data, dstByte, previewSize.width, previewSize.height);
} else if (colorFormat == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar) {
Yuv420Util.Nv21ToI420(data, dstByte, previewSize.width, previewSize.height);
} else if (colorFormat == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible) {
// Yuv420_888
} else if (colorFormat == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar) {
// Yuv420packedPlannar 和 yuv420sp很像
// 区别在于 加入 width = 4的话 y1,y2,y3 ,y4公用 u1v1
// 而 yuv420dp 则是 y1y2y5y6 共用 u1v1
//http://blog.csdn.net/jumper511/article/details/21719313
//这样处理的话颜色核能会有些失真。
Yuv420Util.Nv21ToYuv420SP(data, dstByte, previewSize.width, previewSize.height);
} else {
System.arraycopy(data, 0, dstByte, 0, data.length);
}
onGetVideoFrame(dstByte);
}
camera.addCallbackBuffer(data);
} else {
camera.addCallbackBuffer(new byte[calculateFrameSize(ImageFormat.NV21)]);
}
}
};
}
private void onGetVideoFrame(byte[] dstByte) {
ByteBuffer[] inputBuffers = vencoder.getInputBuffers();
ByteBuffer[] outputBuffers = vencoder.getOutputBuffers();
int inputBufferId = vencoder.dequeueInputBuffer(-1);
if (inputBufferId >= 0) {
// fill inputBuffers[inputBufferId] with valid data
ByteBuffer bb = inputBuffers[inputBufferId];
bb.clear();
bb.put(dstByte, 0, dstByte.length);
long pts = new Date().getTime() * 1000 - presentationTimeUs;
vencoder.queueInputBuffer(inputBufferId, 0, dstByte.length, pts, 0);
}
for (; ; ) {
int outputBufferId = vencoder.dequeueOutputBuffer(vBufferInfo, 0);
if (outputBufferId >= 0) {
// outputBuffers[outputBufferId] is ready to be processed or rendered.
ByteBuffer bb = outputBuffers[outputBufferId];
onEncodedAvcFrame(bb, vBufferInfo);
vencoder.releaseOutputBuffer(outputBufferId, false);
}
if (outputBufferId < 0) {
break;
}
}
}
private void onGetPcmFrame(byte[] data) {
ByteBuffer[] inputBuffers = aencoder.getInputBuffers();
ByteBuffer[] outputBuffers = aencoder.getOutputBuffers();
int inputBufferId = aencoder.dequeueInputBuffer(-1);
if (inputBufferId >= 0) {
ByteBuffer bb = inputBuffers[inputBufferId];
bb.clear();
bb.put(data, 0, data.length);
long pts = new Date().getTime() * 1000 - presentationTimeUs;
aencoder.queueInputBuffer(inputBufferId, 0, data.length, pts, 0);
}
for (; ; ) {
int outputBufferId = aencoder.dequeueOutputBuffer(aBufferInfo, 0);
if (outputBufferId >= 0) {
// outputBuffers[outputBufferId] is ready to be processed or rendered.
ByteBuffer bb = outputBuffers[outputBufferId];
onEncodeAacFrame(bb, aBufferInfo);
aencoder.releaseOutputBuffer(outputBufferId, false);
}
if (outputBufferId < 0) {
break;
}
}
}
private void onEncodedAvcFrame(ByteBuffer bb, MediaCodec.BufferInfo vBufferInfo) {
int offset = 4;
//判断帧的类型
if (bb.get(2) == 0x01) {
offset = 3;
}
int type = bb.get(offset) & 0x1f;
if (type == NAL_SPS) {
//[0, 0, 0, 1, 103, 66, -64, 13, -38, 5, -126, 90, 1, -31, 16, -115, 64, 0, 0, 0, 1, 104, -50, 6, -30]
//打印发现这里将 SPS帧和 PPS帧合在了一起发送
// SPS为 [4,len-8]
// PPS为后4个字节
//so .
byte[] pps = new byte[4];
byte[] sps = new byte[vBufferInfo.size - 12];
bb.getInt();// 抛弃 0,0,0,1
bb.get(sps, 0, sps.length);
bb.getInt();
bb.get(pps, 0, pps.length);
Log.d(TAG, "解析得到 sps:" + Arrays.toString(sps) + ",PPS=" + Arrays.toString(pps));
mRtmpPublisher.sendSpsAndPps(sps, sps.length, pps, pps.length,
vBufferInfo.presentationTimeUs / 1000);
} else {
byte[] bytes = new byte[vBufferInfo.size];
bb.get(bytes);
mRtmpPublisher.sendVideoData(bytes, bytes.length,
vBufferInfo.presentationTimeUs / 1000);
}
}
private void onEncodeAacFrame(ByteBuffer bb, MediaCodec.BufferInfo aBufferInfo) {
// 1.界定符 FF F1
// 2.加上界定符的前7个字节是帧描述信息
// 3.AudioDecoderSpecificInfo 长度为2个字节如果是44100 改值为0x1210
//http://blog.csdn.net/avsuper/article/details/24661533
//http://www.tuicool.com/articles/aYvmua
if (aBufferInfo.size == 2) {
// https://my.oschina.net/zhangxu0512/blog/204070
// faacEncSetConfiguration(m_hEncoder, pConfiguration);
// int ret = faacEncGetDecoderSpecificInfo(m_hEncoder, &m_pSpc, &m_nSpc);
// //AAC sequence header
// int[] mpeg4audio_sample_rates = {
// 96000, 88200, 64000, 48000, 44100, 32000,
// 24000, 22050, 16000, 12000, 11025, 8000, 7350
// };
//
// int m_keyframe[] = new int[2];
// //get keyframe info.
// int index;
// for (index = 0; index < 16; index++) {
// if (aSampleRate == mpeg4audio_sample_rates[index]) {
// break;
// }
// }
// m_keyframe[0] = 0x02 << 3 | index >> 1;
// m_keyframe[1] = (index & 0x01) << 7 | aChanelCount << 3;
//
// Log.d(TAG,"挂件"+Arrays.toString(m_keyframe));
// 我打印发现,这里应该已经是吧关键帧计算好了,所以我们直接发送
byte[] bytes = new byte[2];
bb.get(bytes);
mRtmpPublisher.sendAacSpec(bytes,2);
}else{
byte[] bytes = new byte[aBufferInfo.size];
bb.get(bytes);
mRtmpPublisher.sendAacData(bytes,bytes.length,aBufferInfo.presentationTimeUs/1000);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
- 372
- 373
- 374
- 375
- 376
- 377
- 378
- 379
- 380
- 381
- 382
- 383
- 384
- 385
- 386
- 387
- 388
- 389
- 390
- 391
- 392
- 393
- 394
- 395
- 396
- 397
- 398
- 399
- 400
- 401
- 402
- 403
- 404
- 405
- 406
- 407
- 408
- 409
- 410
- 411
- 412
- 413
- 414
- 415
- 416
- 417
- 418
- 419
- 420
- 421
- 422
- 423
- 424
- 425
- 426
- 427
- 428
- 429
- 430
- 431
- 432
- 433
- 434
- 435
- 436
- 437
- 438
- 439
- 440
- 441
- 442
- 443
- 444
- 445
- 446
- 447
- 448
- 449
- 450
- 451
- 452
- 453
- 454
- 455
- 456
- 457
- 458
- 459
- 460
- 461
- 462
- 463
- 464
- 465
- 466
- 467
- 468
- 469
- 470
- 471
- 472
- 473
- 474
- 475
- 476
- 477
- 478
- 479
- 480
- 481
- 482
- 483
- 484
- 485
- 486
- 487
- 488
- 489
- 490
- 491
- 492
- 493
- 494
- 495
- 496
- 497
- 498
- 499
- 500
- 501
- 502
- 503
- 504
- 505
- 506
- 507
- 508
- 509
- 510
- 511
- 512
- 513
- 514
- 515
- 516
- 517
- 518
- 519
- 520
- 521
- 522
- 523
- 524
- 525
- 526
- 527
- 528
- 529
- 530
- 531
- 532
- 533
- 534
- 535
- 536
- 537
- 538
- 539
- 540
- 541
- 542
- 543
- 544
- 545
- 546
- 547
- 548
- 549
- 550
- 551
- 552
- 553
- 554
- 555
- 556
- 557
- 558
- 559
- 560
- 561
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
- 372
- 373
- 374
- 375
- 376
- 377
- 378
- 379
- 380
- 381
- 382
- 383
- 384
- 385
- 386
- 387
- 388
- 389
- 390
- 391
- 392
- 393
- 394
- 395
- 396
- 397
- 398
- 399
- 400
- 401
- 402
- 403
- 404
- 405
- 406
- 407
- 408
- 409
- 410
- 411
- 412
- 413
- 414
- 415
- 416
- 417
- 418
- 419
- 420
- 421
- 422
- 423
- 424
- 425
- 426
- 427
- 428
- 429
- 430
- 431
- 432
- 433
- 434
- 435
- 436
- 437
- 438
- 439
- 440
- 441
- 442
- 443
- 444
- 445
- 446
- 447
- 448
- 449
- 450
- 451
- 452
- 453
- 454
- 455
- 456
- 457
- 458
- 459
- 460
- 461
- 462
- 463
- 464
- 465
- 466
- 467
- 468
- 469
- 470
- 471
- 472
- 473
- 474
- 475
- 476
- 477
- 478
- 479
- 480
- 481
- 482
- 483
- 484
- 485
- 486
- 487
- 488
- 489
- 490
- 491
- 492
- 493
- 494
- 495
- 496
- 497
- 498
- 499
- 500
- 501
- 502
- 503
- 504
- 505
- 506
- 507
- 508
- 509
- 510
- 511
- 512
- 513
- 514
- 515
- 516
- 517
- 518
- 519
- 520
- 521
- 522
- 523
- 524
- 525
- 526
- 527
- 528
- 529
- 530
- 531
- 532
- 533
- 534
- 535
- 536
- 537
- 538
- 539
- 540
- 541
- 542
- 543
- 544
- 545
- 546
- 547
- 548
- 549
- 550
- 551
- 552
- 553
- 554
- 555
- 556
- 557
- 558
- 559
- 560
- 561
MainActivity 中的代码主要是采集视频、音频进行编码。然后调用jni方法进行发送。
在c层。我封装了一个类用来发送音视频数据:
class Rtmp {
private:
int width;
int height;
int timeOut;
std::string url;
long startTime;
RTMP *rtmp;
public:
/**
* 初始化
*/
virtual int init(std::string url, int w, int h, int timeOut);
/**
* 发送sps、pps 帧
*/
virtual int sendSpsAndPps(BYTE *sps, int spsLen, BYTE *pps, int ppsLen,
long timestamp);
/**
* 发送视频帧
*/
virtual int sendVideoData(BYTE *data, int len, long timestamp);
/**
* 发送音频关键帧
*/
virtual int sendAacSpec(BYTE *data, int len);
/**
* 发送音频数据
*/
virtual int sendAacData(BYTE *data, int len,long timestamp);
/**
* 释放资源
*/
virtual int stop() const;
virtual ~Rtmp();
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
实现:
//
// Created by Administrator on 1/16/2017.
//
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
观看
我们如果测试的话,我们首先需要搭建一个流媒体服务器。可以在本地安装一个Adobe Media Server 。然后打开它其中的一个示例。比如我安装在D盘Program File 文件夹下
那么我打开D:\Program Files\Adobe\Adobe Media Server 5\samples\videoPlayer\videoplayer.html
输入推流的地址就可以播放了。当然手机和电脑记得处于同一个局域网。
完整代码地址 https://github.com/blueberryCoder/LiveStream
-
顶
- 2
-
踩
- 0
- 上一篇使用librtmp推送AVC数据
- 下一篇Android知识点总结
我的同类文章
- •IntentFilter的匹配规则2017-02-07
- •使用librtmp推送AVC数据2017-01-16
- •Android使用系统API进行音视频编码2017-01-09
- •Android移植带有libx264实现的ffmpeg2016-12-30
- •使用Ubuntu16下载编译Android6.0源码2016-12-05
- •Android知识点总结2017-01-23
- •Android移植librtmp2017-01-12
- •Android视频编码器2017-01-04
- •Android使用libjpeg实现图片压缩2016-12-22
- •Android Studio 2.2 NDK开发 opencv 人脸识别2016-11-17
猜你在找
Python编程基础视频教程(第五季)
Python编程基础视频教程(第二季)
PHP网站搭建入门
微信公众平台开发入门
Java之路
C++实现RTMP协议发送H264编码及AAC编码的音视频摄像头直播
RTMP协议发送H264编码及AAC编码的音视频实现摄像头直播
C++实现RTMP协议发送H264编码及AAC编码的音视频
C++实现RTMP协议发送H264编码及AAC编码的音视频摄像头直播
C++实现RTMP协议发送H264编码及AAC编码的音视频
3楼 a521314963 2017-05-04 09:29发表 [回复] [引用] [举报]-
-
博主你的这个VLC能播吗
2楼 ctxx61 2017-04-12 14:44发表 [回复] [引用] [举报]-
-
想问一下 如果只想将采集音频编码并上传应该怎么办呢?老师课题要搞一个音频直播电台,但是查了好久都是说的视频直播
1楼 qq_15931971 2017-03-17 11:56发表 [回复] [引用] [举报]-
-
假如能出个视频教程就好!现在这类的视频教程太少了!我在AS上无法加载!不知道为什么
Re: a521314963 2017-05-04 09:46发表 [回复] [引用] [举报]-
-
回复ctxx61: 你的音频播出来了吗?