自动采集推送( 相比较视频编码,音频编码要简单很多(组图))
优采云 发布时间: 2021-10-28 18:15自动采集推送(
相比较视频编码,音频编码要简单很多(组图))
与视频编码相比,音频编码要简单得多。主要是将采集音频源数据PCM编码为AAC。
MediaPlus 中的 FFmpeg 使用 libfdk-aac 编码器。有一个问题需要注意:FFmpeg已经放弃了AV_SAMPLE_FMT_S16格式的PCM编码AAC,这意味着如果你使用FFmpeg自带的AAC编码器,必须做音频重采样(重采样为:AV_SAMPLE_FMT_FLTP),否则AAC编码会失败.
接下来,我们来看看采集音频和AAC在MediaPlus中是如何编码的。
从.libmedia.core.streamer.RtmpPushStreamer的AudioThread中的AudioRecord采集中获取音频数据并传递给底层:
class AudioThread extends Thread {
public volatile boolean m_bExit = false;
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
int[] dataLength;
byte[] audioBuffer;
AudioCaptureInterface.GetAudioDataReturn ret;
dataLength = new int[1];
audioBuffer = new byte[m_aiBufferLength[0]];
while (!m_bExit) {
try {
Thread.sleep(1, 10);
if (m_bExit) {
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
ret = mAudioCapture.GetAudioData(audioBuffer,
m_aiBufferLength[0], dataLength);
if (ret == AudioCaptureInterface.GetAudioDataReturn.RET_SUCCESS) {
encodeAudio(audioBuffer, dataLength[0]);
}
} catch (Exception e) {
e.printStackTrace();
stopThread();
}
}
}复制代码
AudioRecord采集的具体实现在avcapture.jar包中。代码比较简单。 Android音视频采集初始化和API调用的相关Demo网上有,这里不再赘述!
int AudioCapture::PushAudioData(OriginData *originData) {
if (ExitCapture) {
return 0;
}
originData->pts = av_gettime();
LOG_D(DEBUG,"audio capture pts :%lld",originData->pts);
audioCaputureframeQueue.push(originData);
return 0;
}复制代码
以上代码和视频处理是同一个过程。调用.libmedia.core.streamer.RtmpPushStreamer>>startPushStream()后,数据已经加入音频队列,然后rtmpStreamer->StartPushStream(),其实就是把两个编码线程和音视频推流上,推流相关代码与视频相同。
int RtmpStreamer::StartPushStream() {
videoStreamIndex = AddStream(videoEncoder->videoCodecContext);
audioStreamIndex = AddStream(audioEncoder->audioCodecContext);
pthread_create(&t3, NULL, RtmpStreamer::WriteHead, this);
pthread_join(t3, NULL);
VideoCapture *pVideoCapture = videoEncoder->GetVideoCapture();
AudioCapture *pAudioCapture = audioEncoder->GetAudioCapture();
pVideoCapture->videoCaputureframeQueue.clear();
pAudioCapture->audioCaputureframeQueue.clear();
if(writeHeadFinish) {
pthread_create(&t1, NULL, RtmpStreamer::PushAudioStreamTask, this);
pthread_create(&t2, NULL, RtmpStreamer::PushVideoStreamTask, this);
}else{
return -1;
}
return 0;
}复制代码
这里解释一下,在音频编码之前获取编码器和一些参数名称:
libmedia/src/main/cpp/AudioEncoder.cpp 是音频编码的核心类。 int AudioEncoder::InitEncode() 方法封装了音频编码器的初始化。
int AudioEncoder::InitEncode() {
std::lock_guard lk(mut);
avCodec = avcodec_find_encoder_by_name("libfdk_aac");
int ret = 0;
if (!avCodec) {
LOG_D(DEBUG, "aac encoder not found!")
return -1;
}
audioCodecContext = avcodec_alloc_context3(avCodec);
if (!audioCodecContext) {
LOG_D(DEBUG, "avcodec alloc context3 failed!");
return -1;
}
audioCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
audioCodecContext->sample_fmt = AV_SAMPLE_FMT_S16;
audioCodecContext->sample_rate = audioCapture->GetAudioEncodeArgs()->sampleRate;
audioCodecContext->thread_count = 8;
audioCodecContext->bit_rate = 50*1024*8;
audioCodecContext->channels = audioCapture->GetAudioEncodeArgs()->channels;
audioCodecContext->frame_size = audioCapture->GetAudioEncodeArgs()->nb_samples;
audioCodecContext->time_base = {1, 1000000};//AUDIO VIDEO 两边时间基数要相同
audioCodecContext->channel_layout = av_get_default_channel_layout(audioCodecContext->channels);
outputFrame = av_frame_alloc();
outputFrame->channels = audioCodecContext->channels;
outputFrame->channel_layout = av_get_default_channel_layout(outputFrame->channels);
outputFrame->format = audioCodecContext->sample_fmt;
outputFrame->nb_samples = 1024;
ret = av_frame_get_buffer(outputFrame, 0);
if (ret != 0) {
LOG_D(DEBUG, "av_frame_get_buffer failed!");
return -1;
}
LOG_D(DEBUG, "av_frame_get_buffer success!");
ret = avcodec_open2(audioCodecContext, NULL, NULL);
if (ret != 0) {
char buf[1024] = {0};
av_strerror(ret, buf, sizeof(buf));
LOG_D(DEBUG, "avcodec open failed! info:%s", buf);
return -1;
}
LOG_D(DEBUG, "open audio codec success!");
LOG_D(DEBUG, "Complete init Audio Encode!")
return 0;
}复制代码
int AudioEncoder::EncodeAAC方法封装了AAC编码:
int AudioEncoder::EncodeAAC(OriginData **originData) {
int ret = 0;
ret = avcodec_fill_audio_frame(outputFrame,
audioCodecContext->channels,
audioCodecContext->sample_fmt, (*originData)->data,
8192, 0);
outputFrame->pts = (*originData)->pts;
ret = avcodec_send_frame(audioCodecContext, outputFrame);
if (ret != 0) {
#ifdef SHOW_DEBUG_INFO
LOG_D(DEBUG, "send frame failed!");
#endif
}
av_packet_unref(&audioPacket);
ret = avcodec_receive_packet(audioCodecContext, &audioPacket);
if (ret != 0) {
#ifdef SHOW_DEBUG_INFO
LOG_D(DEBUG, "receive packet failed!");
#endif
}
(*originData)->Drop();
(*originData)->avPacket = &audioPacket;
#ifdef SHOW_DEBUG_INFO
LOG_D(DEBUG, "encode audio packet size:%d pts:%lld", (*originData)->avPacket->size,
(*originData)->avPacket->pts);
LOG_D(DEBUG, "Audio frame encode success!");
#endif
(*originData)->avPacket->size;
return audioPacket.size;
}复制代码
注意:在 int AudioEncoder::InitEncode() 方法中
avcodec_find_encoder_by_name("libfdk_aac");复制代码
这里使用的是fdk-aac编码器,前提是必须将libfdk-aac库链接到ffmpeg动态库,否则找不到编码器。 FFmpeg自带AAC编码器,可以通过:
avcodec_find_encoder(AV_CODEC_ID_AAC);复制代码
获取 AAC 编码器。当然,如果使用FFmpeg的AAC编码器,就会有问题。文章开头提到,AV_SAMPLE_FMT_S16需要重采样为:AV_SAMPLE_FMT_FLTP。因为FFmpeg放弃了AV_SAMPLE_FMT_S16格式的PCM编码AAC,那么编码前还需要多一步重采样。
以下AV_SAMPLE_FMT_S16 PCM音频数据重采样相关代码仅供参考:
以上简单介绍了android采集音频PCM数据和AAC编码,AAC编码涉及的相关初始化,以及FFmpeg AAC编码器的重采样示例。 android*敏*感*词*采集、H264编码和Rtmp流媒体本文介绍了音视频采集、编码过程以及如何完成推送,相关文章待续...