NDK开发——FFmpeg实现视频转YUV、视频转RGB显示、音频转PCM、音频播放、音视频同步

2023-11-08

项目演示

这里写图片描述

前提准备

  1. 编译FFmpeg+CMake并能运行,详细可见我博客
  2. 下载libyuv库并编译成libyuv.so库,用于实现转换RGB格式功能

FFmpeg库简介

  • avcodec:编解码,包含
  • avformate:封装格式处理
  • avfilter:滤镜特效处理
  • avdevice:输入输出设备
  • avutil:工具库
  • swresample:音频采样处理
  • swscale:视频像素格式转换,缩放等

FFmpeg解码流程

这里写图片描述

流程从上到下分别为

  1. 注册所有组件
  2. 打开视频文件
  3. 获取视频信息
  4. 获取解码器
  5. 打开解码器
  6. 循环解析每一帧数据

FFmpeg的数据结构

  • AVFormatContext:封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息
    • iformat:输入视频的AVInputFormat
    • nb_streams :输入视频的AVStream 个数
    • streams :输入视频的AVStream []数组
    • duration :输入视频的时长(以微秒为单位)
    • bit_rate :输入视频的码率
  • AVInputFormat:每种封装格式(例如FLV, MKV, MP4, AVI)对应一个该结构体
    • name:封装格式名称
    • long_name:封装格式的长名称
    • extensions:封装格式的扩展名
    • id:封装格式ID
  • AVStream:视频文件中每个视频(音频)流对应一个该结构体
    • id:序号
    • codec:该流对应的AVCodecContext
    • time_base:该流的时基
    • r_frame_rate:该流的帧率
  • AVCodecContext:编码器上下文结构体,保存了视频(音频)编解码相关信息
    • codec:编解码器的AVCodec
    • width, height:图像的宽高(只针对视频)
    • pix_fmt:像素格式(只针对视频)
    • sample_rate:采样率(只针对音频)
    • channels:声道数(只针对音频)
    • sample_fmt:采样格式(只针对音频)
  • AVCodec:每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体
    • name:编解码器名称
    • long_name:编解码器长名称
    • type:编解码器类型
    • id:编解码器ID
  • AVPacket:存储一帧压缩编码数据
    • pts:显示时间戳
    • dts :解码时间戳
    • data :压缩编码数据
    • size :压缩编码数据大小
    • stream_index :所属的AVStream
  • AVFrame:存储一帧解码后像素(采样)数据
    • data:解码后的图像像素数据(音频采样数据)
    • linesize:对视频来说是图像中一行像素的大小;对音频来说是音频帧的大小
    • width, height:图像的宽高(只针对视频)
    • key_frame:是否为关键帧(只针对视频)
    • pict_type:帧类型(只针对视频) 。例如I,P,B

项目结构

这里写图片描述

  • native-lib.cpp:文件中实现的功能有,视频转YUV、视频转RGB显示、音频转PCM、音频播放
  • native-player.cpp:文件中实现的功能有,音视频同步
  • queue.cpp:文件中实现的功能有,提供一个队列可以存放AVPacket,用于音视频同步
  • FFmpegUtils:存放本地静态方法
  • MainActivity:主界面
  • VideoView:用于视频的播放界面,为屏幕下面黑屏部分

本地方法

public class FFmpegUtils {

    static {
        System.loadLibrary("avutil-54");
        System.loadLibrary("swresample-1");
        System.loadLibrary("avcodec-56");
        System.loadLibrary("avformat-56");
        System.loadLibrary("swscale-3");
        System.loadLibrary("postproc-53");
        System.loadLibrary("avfilter-5");
        System.loadLibrary("avdevice-56");
        System.loadLibrary("yuv");
        System.loadLibrary("native-lib");
        System.loadLibrary("native-player");
    }

    /**
     * 视频转换输出YUV格式文件
     *
     * @param input_path
     * @param output_path
     */
    public static native void video2YUV(String input_path, String output_path);

    /**
     * 视频转换显示RGB格式
     *
     * @param video_path
     * @param surface
     */
    public static native void video2RGB(String video_path, Surface surface);

    /**
     * 音频转换输出PCM文件
     *
     * @param input_path
     * @param output_path
     */
    public static native void sound2PCM(String input_path, String output_path);


    /**
     * 播放音频
     *
     * @param input_path
     */
    public native void soundPlay(String input_path);

    /**
     * 播放音视频
     *
     * @param input_path
     * @param surface
     */
    public native void videoAndSoundPlay(String input_path, Surface surface);


    /**
     * 创建一个AudioTrack对象,用于播放
     *
     * @param nb_channels
     * @return
     */
    public AudioTrack createAudioTrack(int nb_channels) {
        //固定的比特率
        int sampleRateInHz = 44100;
        //固定格式的音频码流
        int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
        //声道布局
        int channelConfig;
        if (nb_channels == 1) {
            channelConfig = android.media.AudioFormat.CHANNEL_OUT_MONO;
        } else if (nb_channels == 2) {
            channelConfig = android.media.AudioFormat.CHANNEL_OUT_STEREO;
        } else {
            channelConfig = android.media.AudioFormat.CHANNEL_OUT_STEREO;
        }
        int bufferSizeInBytes = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);

        AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, channelConfig,
                audioFormat, bufferSizeInBytes, AudioTrack.MODE_STREAM);
        return audioTrack;
    }

}

CmakeLists

cmake_minimum_required(VERSION 3.4.1)


include_directories(./libs/include
                    ./libs/include/libyuv)

link_directories(./libs/${ANDROID_ABI})

find_library(log-lib
             log)
find_library(android-lib
             android)

add_library(
             native-lib
             SHARED
             src/main/cpp/native-lib.cpp )
add_library(
             native-player
             SHARED
             src/main/cpp/native-player.cpp
             src/main/cpp/queue.cpp)

target_link_libraries(native-lib
                       ${log-lib}
                       ${android-lib}
                       avutil-54
                       swresample-1
                       avcodec-56
                       avformat-56
                       swscale-3
                       postproc-53
                       avfilter-5
                       avdevice-56
                       yuv)
target_link_libraries(native-player
                       ${log-lib}
                       ${android-lib}
                       avutil-54
                       swresample-1
                       avcodec-56
                       avformat-56
                       swscale-3
                       postproc-53
                       avfilter-5
                       avdevice-56
                       yuv)

权限声明

由于需要将生成的文件放入SD卡中,所以需要相应的权限

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 往SDCard写入数据权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 在SDCard中创建与删除文件权限 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />

头文件

注意增加extern “C” 写法,兼容c

#include <jni.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <android/log.h>

#include <android/native_window.h>
#include <android/native_window_jni.h>

extern "C" {
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include "libyuv/libyuv.h"
}

#define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,"ffmpeg",FORMAT,##__VA_ARGS__);
//16bit 44100 PCM 数据大小
#define MAX_AUDIO_FRME_SIZE 44100 * 2

视频转YUV

将视频文件格式转为指定的YUV420P像素帧,并生成YUV新文件

extern "C"
JNIEXPORT void JNICALL
Java_com_handsome_ndkffmpeg_FFmpegUtils_video2YUV(JNIEnv *env, jclass jclazz, jstring input_path_,
                                                  jstring out_path_) {
    const char *input_path = env->GetStringUTFChars(input_path_, NULL);
    const char *output_path = env->GetStringUTFChars(out_path_, NULL);
    //1、注册所有组件
    av_register_all();
    //2、打开视频文件
    AVFormatContext *pFormatCtx = avformat_alloc_context();
    if ((avformat_open_input(&pFormatCtx, input_path, NULL, NULL)) < 0) {
        LOGE("Cannot open input file");
        return;
    }
    //3、获取视频信息
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        LOGE("Cannot find stream\n");
        return;
    }
    //4、找到视频流的位置
    int video_stream_index = -1;
    int i = 0;
    for (; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_stream_index = i;
            LOGE("find the stream index %d", video_stream_index);
            break;
        }
    }
    //5、获取解码器
    AVCodecContext *pCodeCtx = pFormatCtx->streams[video_stream_index]->codec;
    AVCodec *pCodec = avcodec_find_decoder(pCodeCtx->codec_id);
    if (pCodec == NULL) {
        LOGE("Cannot find decoder\n");
        return;
    }
    //6、打开解码器
    if (avcodec_open2(pCodeCtx, pCodec, NULL) < 0) {
        LOGE("Cannot open codec\n");
        return;
    }
    //7、解析每一帧数据
    int got_picture_ptr, frame_count = 1;
    //压缩数据
    AVPacket *packet = (AVPacket *) av_malloc(sizeof(AVPacket));
    //解压缩数据
    AVFrame *frame = av_frame_alloc();
    AVFrame *yuvFrame = av_frame_alloc();

    //将视频转换成指定的420P的YUV格式
    //缓冲区分配内存
    uint8_t *out_buffer = (uint8_t *) av_malloc(
            avpicture_get_size(AV_PIX_FMT_YUV420P, pCodeCtx->width, pCodeCtx->height));
    //初始化缓冲区
    avpicture_fill((AVPicture *) yuvFrame, out_buffer, AV_PIX_FMT_YUV420P, pCodeCtx->width,
                   pCodeCtx->height);
    //用于像素格式转换或者缩放
    struct SwsContext *sws_ctx = sws_getContext(
            pCodeCtx->width, pCodeCtx->height, pCodeCtx->pix_fmt,
            pCodeCtx->width, pCodeCtx->height, AV_PIX_FMT_YUV420P,
            SWS_BILINEAR, NULL, NULL, NULL);
    //输出文件
    FILE *fp_yuv = fopen(output_path, "wb");
    //一帧一帧读取压缩的视频数据
    while (av_read_frame(pFormatCtx, packet) >= 0) {
        //找到视频流
        if (packet->stream_index == video_stream_index) {
            avcodec_decode_video2(pCodeCtx, frame, &got_picture_ptr, packet);
            //正在解码
            if (got_picture_ptr) {
                //frame->yuvFrame,转为指定的YUV420P像素帧
                sws_scale(sws_ctx, (const uint8_t *const *) frame->data, frame->linesize, 0,
                          frame->height, yuvFrame->data, yuvFrame->linesize);
                //计算视频数据总大小
                int y_size = pCodeCtx->width * pCodeCtx->height;
                //AVFrame->YUV,由于YUV的比例是4:1:1
                fwrite(yuvFrame->data[0], 1, y_size, fp_yuv);
                fwrite(yuvFrame->data[1], 1, y_size / 4, fp_yuv);
                fwrite(yuvFrame->data[2], 1, y_size / 4, fp_yuv);
                LOGE("解析第%d帧", (frame_count++));
            }
            av_free_packet(packet);
        }
    }
    //8、释放资源
    fclose(fp_yuv);
    av_frame_free(&frame);
    avcodec_close(pCodeCtx);
    avformat_free_context(pFormatCtx);
    env->ReleaseStringUTFChars(input_path_, input_path);
    env->ReleaseStringUTFChars(out_path_, output_path);
}

视频转RGB显示

1、在屏幕上需要一个SurfaceView来显示我们的视频,设置一下其显示的格式,与FFmepg转换的格式匹配

public class VideoView extends SurfaceView {
    public VideoView(Context context) {
        this(context, null);
    }

    public VideoView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VideoView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public void init() {
        getHolder().setFormat(PixelFormat.RGBA_8888);
    }
}

2、采用libyuv的转换方法,将YUV格式转换成RGB格式,并在SurfaceView上显示

extern "C"
JNIEXPORT void JNICALL
Java_com_handsome_ndkffmpeg_FFmpegUtils_video2RGB(JNIEnv *env, jclass type, jstring input_path_,
                                                  jobject surface) {
    const char *input_path = env->GetStringUTFChars(input_path_, 0);
    //1、注册所有组件
    av_register_all();
    //2、打开视频文件
    AVFormatContext *pFormatCtx = avformat_alloc_context();
    if ((avformat_open_input(&pFormatCtx, input_path, NULL, NULL)) < 0) {
        LOGE("Cannot open input file");
        return;
    }
    //3、获取视频信息
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        LOGE("Cannot find stream\n");
        return;
    }
    //4、找到视频流的位置
    int video_stream_index = -1;
    int i = 0;
    for (; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_stream_index = i;
            LOGE("find the stream index %d", video_stream_index);
            break;
        }
    }
    //5、获取解码器
    AVCodecContext *pCodeCtx = pFormatCtx->streams[video_stream_index]->codec;
    AVCodec *pCodec = avcodec_find_decoder(pCodeCtx->codec_id);
    if (pCodec == NULL) {
        LOGE("Cannot find decoder\n");
        return;
    }
    //6、打开解码器
    if (avcodec_open2(pCodeCtx, pCodec, NULL) < 0) {
        LOGE("Cannot open codec\n");
        return;
    }
    //7、解析每一帧数据
    int got_picture_ptr, frame_count = 1;
    //压缩数据
    AVPacket *packet = (AVPacket *) av_malloc(sizeof(AVPacket));
    //解压缩数据
    AVFrame *yuv_frame = av_frame_alloc();
    AVFrame *rgb_frame = av_frame_alloc();
    //绘制时的surface窗口
    ANativeWindow *window = ANativeWindow_fromSurface(env, surface);
    //绘制时的缓冲区
    ANativeWindow_Buffer outBuffer;
    //一帧一帧读取压缩的视频数据
    while (av_read_frame(pFormatCtx, packet) >= 0) {
        //找到视频流
        if (packet->stream_index == video_stream_index) {
            avcodec_decode_video2(pCodeCtx, yuv_frame, &got_picture_ptr, packet);
            //正在解码
            if (got_picture_ptr) {
                LOGE("解码%d帧", frame_count++);
                //设置缓冲区的属性(宽、高、像素格式)
                ANativeWindow_setBuffersGeometry(window, pCodeCtx->width, pCodeCtx->height,
                                                 WINDOW_FORMAT_RGBA_8888);
                ANativeWindow_lock(window, &outBuffer, NULL);
                //设置rgb_frame的属性(像素格式、宽高)和缓冲区
                //rgb_frame缓冲区与outBuffer.bits是同一块内存
                avpicture_fill((AVPicture *) rgb_frame, (const uint8_t *) outBuffer.bits,
                               PIX_FMT_RGBA, pCodeCtx->width, pCodeCtx->height);
                //YUV->RGBA_8888
                libyuv::I420ToARGB(yuv_frame->data[0], yuv_frame->linesize[0],
                                   yuv_frame->data[2], yuv_frame->linesize[2],
                                   yuv_frame->data[1], yuv_frame->linesize[1],
                                   rgb_frame->data[0], rgb_frame->linesize[0],
                                   pCodeCtx->width, pCodeCtx->height);
                //unlock
                ANativeWindow_unlockAndPost(window);
                //绘制停顿16ms
                usleep(1000 * 16);
            }
            av_free_packet(packet);
        }
    }
    //8、释放资源
    ANativeWindow_release(window);
    av_frame_free(&yuv_frame);
    avcodec_close(pCodeCtx);
    avformat_free_context(pFormatCtx);
    env->ReleaseStringUTFChars(input_path_, input_path);
}

音频转PCM

将音频采样进行重采样,获取我们需要的PCM格式的音频文件,并生成PCM新文件

extern "C"
JNIEXPORT void JNICALL
Java_com_handsome_ndkffmpeg_FFmpegUtils_sound2PCM(JNIEnv *env, jclass type, jstring input_path_,
                                                  jstring output_path_) {
    const char *input_path = env->GetStringUTFChars(input_path_, 0);
    const char *output_path = env->GetStringUTFChars(output_path_, 0);
    //1、注册所有组件
    av_register_all();
    //2、打开视频文件
    AVFormatContext *pFormatCtx = avformat_alloc_context();
    if ((avformat_open_input(&pFormatCtx, input_path, NULL, NULL)) < 0) {
        LOGE("Cannot open input file");
        return;
    }
    //3、获取视频信息
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        LOGE("Cannot find stream\n");
        return;
    }
    //4、找到视频流的位置
    int audio_stream_index = -1;
    int i = 0;
    for (; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
            audio_stream_index = i;
            LOGE("find the stream index %d", audio_stream_index);
            break;
        }
    }
    //5、获取解码器
    AVCodecContext *pCodeCtx = pFormatCtx->streams[audio_stream_index]->codec;
    AVCodec *pCodec = avcodec_find_decoder(pCodeCtx->codec_id);
    if (pCodec == NULL) {
        LOGE("Cannot find decoder\n");
        return;
    }
    //6、打开解码器
    if (avcodec_open2(pCodeCtx, pCodec, NULL) < 0) {
        LOGE("Cannot open codec\n");
        return;
    }
    //7、解析每一帧数据(包含重采样)
    int got_picture_ptr, frame_count = 1;
    //压缩数据
    AVPacket *packet = (AVPacket *) av_malloc(sizeof(AVPacket));
    //解压缩数据
    AVFrame *frame = av_frame_alloc();

    //重采样设置参数,将frame数据转成16bit比特率44100的PCM格式
    //重采样上下文
    SwrContext *swrCtx = swr_alloc();
    //输入的采样格式
    enum AVSampleFormat in_sample_fmt = pCodeCtx->sample_fmt;
    //输出采样格式16bit的PCM
    enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
    //输入采样率
    int in_sample_rate = pCodeCtx->sample_rate;
    //输出采样率
    int out_sample_rate = 44100;
    //获取输入的声道布局
    uint64_t in_ch_layout = pCodeCtx->channel_layout;
    //输出的声道布局(立体声)
    uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
    //设置重采样配置
    swr_alloc_set_opts(swrCtx,
                       out_ch_layout, out_sample_fmt, out_sample_rate,
                       in_ch_layout, in_sample_fmt, in_sample_rate,
                       0, NULL);
    //重采样初始化
    swr_init(swrCtx);
    //获取输出的声道个数
    int out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout);
    //16bit 44100 PCM 数据
    uint8_t *out_buffer = (uint8_t *) av_malloc(MAX_AUDIO_FRME_SIZE);
    //输出文件
    FILE *fp_pcm = fopen(output_path, "wb");
    //一帧一帧读取压缩的视频数据
    while (av_read_frame(pFormatCtx, packet) >= 0) {
        //找到音频流
        if (packet->stream_index == audio_stream_index) {
            avcodec_decode_audio4(pCodeCtx, frame, &got_picture_ptr, packet);
            //正在解码
            if (got_picture_ptr) {
                //重采样转换
                swr_convert(swrCtx, &out_buffer, MAX_AUDIO_FRME_SIZE,
                            (const uint8_t **) frame->data,
                            frame->nb_samples);
                //获取采样的大小
                int out_buffer_size = av_samples_get_buffer_size(NULL, out_channel_nb,
                                                                 frame->nb_samples, out_sample_fmt,
                                                                 1);
                fwrite(out_buffer, 1, out_buffer_size, fp_pcm);
                LOGE("解析第%d帧", (frame_count++));
            }
            av_free_packet(packet);
        }
    }
    //8、释放资源
    fclose(fp_pcm);
    av_frame_free(&frame);
    av_free(out_buffer);
    swr_free(&swrCtx);
    avcodec_close(pCodeCtx);
    avformat_close_input(&pFormatCtx);
    env->ReleaseStringUTFChars(input_path_, input_path);
    env->ReleaseStringUTFChars(output_path_, output_path);
}

音频播放

这里可以采用OpenSL完全在Native层处理播放音频,或者在Java层采用AudioTrack播放,这里演示采用后者,所以需要通过JNI调用Java方法

extern "C"
JNIEXPORT void JNICALL
Java_com_handsome_ndkffmpeg_FFmpegUtils_soundPlay(JNIEnv *env, jobject instance,
                                                  jstring input_path_) {
    const char *input_path = env->GetStringUTFChars(input_path_, 0);
    //1、注册所有组件
    av_register_all();
    //2、打开视频文件
    AVFormatContext *pFormatCtx = avformat_alloc_context();
    if ((avformat_open_input(&pFormatCtx, input_path, NULL, NULL)) < 0) {
        LOGE("Cannot open input file");
        return;
    }
    //3、获取视频信息
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        LOGE("Cannot find stream\n");
        return;
    }
    //4、找到视频流的位置
    int audio_stream_index = -1;
    int i = 0;
    for (; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
            audio_stream_index = i;
            LOGE("find the stream index %d", audio_stream_index);
            break;
        }
    }
    //5、获取解码器
    AVCodecContext *pCodeCtx = pFormatCtx->streams[audio_stream_index]->codec;
    AVCodec *pCodec = avcodec_find_decoder(pCodeCtx->codec_id);
    if (pCodec == NULL) {
        LOGE("Cannot find decoder\n");
        return;
    }
    //6、打开解码器
    if (avcodec_open2(pCodeCtx, pCodec, NULL) < 0) {
        LOGE("Cannot open codec\n");
        return;
    }
    //7、解析每一帧数据(包含重采样)
    int got_picture_ptr, frame_count = 1;
    //压缩数据
    AVPacket *packet = (AVPacket *) av_malloc(sizeof(AVPacket));
    //解压缩数据
    AVFrame *frame = av_frame_alloc();

    //重采样设置参数,将frame数据转成16bit比特率44100的PCM格式
    //重采样上下文
    SwrContext *swrCtx = swr_alloc();
    //输入的采样格式
    enum AVSampleFormat in_sample_fmt = pCodeCtx->sample_fmt;
    //输出采样格式16bit的PCM
    enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
    //输入采样率
    int in_sample_rate = pCodeCtx->sample_rate;
    //输出采样率
    int out_sample_rate = 44100;
    //获取输入的声道布局
    uint64_t in_ch_layout = pCodeCtx->channel_layout;
    //输出的声道布局(立体声)
    uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
    //设置重采样配置
    swr_alloc_set_opts(swrCtx,
                       out_ch_layout, out_sample_fmt, out_sample_rate,
                       in_ch_layout, in_sample_fmt, in_sample_rate,
                       0, NULL);
    //重采样初始化
    swr_init(swrCtx);
    //获取输出的声道个数
    int out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout);
    //16bit 44100 PCM 数据大小
    uint8_t *out_buffer = (uint8_t *) av_malloc(MAX_AUDIO_FRME_SIZE);

    //在读取之前拿到AudioTrack
    //begin--JNI
    jclass player_class = env->GetObjectClass(instance);
    //获取AudioTrack对象
    jmethodID create_audio_track_mid = env->GetMethodID(player_class, "createAudioTrack",
                                                        "(I)Landroid/media/AudioTrack;");
    jobject audio_track = env->CallObjectMethod(instance, create_audio_track_mid, out_channel_nb);
    //调用AudioTrack.play方法
    jclass audio_track_class = env->GetObjectClass(audio_track);
    jmethodID audio_track_play_mid = env->GetMethodID(audio_track_class, "play", "()V");
    env->CallVoidMethod(audio_track, audio_track_play_mid);
    //获取AudioTrack.write
    jmethodID audio_track_write_mid = env->GetMethodID(audio_track_class, "write", "([BII)I");
    //end--JNI

    //一帧一帧读取压缩的视频数据
    while (av_read_frame(pFormatCtx, packet) >= 0) {
        //找到音频流
        if (packet->stream_index == audio_stream_index) {
            avcodec_decode_audio4(pCodeCtx, frame, &got_picture_ptr, packet);
            //正在解码
            if (got_picture_ptr) {
                //重采样转换
                swr_convert(swrCtx, &out_buffer, MAX_AUDIO_FRME_SIZE,
                            (const uint8_t **) frame->data,
                            frame->nb_samples);
                //获取采样的大小
                int out_buffer_size = av_samples_get_buffer_size(NULL, out_channel_nb,
                                                                 frame->nb_samples, out_sample_fmt,
                                                                 1);
                //播放每一帧的音频
                //begin--JNI
                //out_buffer_size缓冲区数据,转成byte数组
                jbyteArray audio_sample_array = env->NewByteArray(out_buffer_size);
                jbyte *sample_byte = env->GetByteArrayElements(audio_sample_array, NULL);
                //out_buffer的数据复制到sample_byte
                memcpy(sample_byte, out_buffer, out_buffer_size);
                //同步数据
                env->ReleaseByteArrayElements(audio_sample_array, sample_byte, 0);
                //调用AudioTrack.write
                env->CallIntMethod(audio_track, audio_track_write_mid,
                                   audio_sample_array, 0, out_buffer_size);
                //释放局部引用
                env->DeleteLocalRef(audio_sample_array);
                usleep(1000 * 16);
                //end--JNI
                LOGE("解析第%d帧", (frame_count++));
            }
            av_free_packet(packet);
        }
    }
    //8、释放资源
    av_frame_free(&frame);
    av_free(out_buffer);
    swr_free(&swrCtx);
    avcodec_close(pCodeCtx);
    avformat_close_input(&pFormatCtx);
    env->ReleaseStringUTFChars(input_path_, input_path);
}

音视频同步

思路分析

  1. 采用队列的形式,循环的读取视频和音频的信息,存储在队列中
  2. 采用生产消费模式,队列生产,则通过线程消费,进行音视频播放
  3. 采用两个队列分别是音频队列、视频队列
  4. 采用三个线程分别是读取音视频数据、播放视频、播放音频

存在问题

  1. 在读取到最后一帧的时候,如果直接break代码,将导致闪退,解决方法是在读取音视频完成时,将代码进入死循环,这个时候会导致主线程阻塞
  2. 在读取音视频的时候并没有加入延迟计算来播放音视频,具体可以参考ffplay的代码进行时间的计算,来实现更准确的同步播放

解决Bug工具

  1. 采用toolchains里面的addr2line工具,使用方法如下
E:\Eclipse\android-studio-sdk\android-sdk-windows\ndk-bundle\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin>arm-linux-androideabi-addr2line -e D:\workspace6\NDKFFmpeg\app\build\intermediates\cmake\debug\obj\armeabi\libnative-player.so 00001e94

//运行出现错误的行数
D:\workspace6\NDKFFmpeg\app\src\main\cpp/native-player.cpp:225

队列头文件

#include <malloc.h>
#include <android/log.h>

typedef struct _Queue Queue;
//分配队列元素内存的函数
typedef void *(*queue_fill_func)();
//释放队列中元素所占用的内存
typedef void *(*queue_free_func)(void *elem);

/**
 * 初始化队列
 */
Queue *queue_init(int size, queue_fill_func fill_func);

/**
 * 销毁队列
 */
void queue_free(Queue *queue, queue_free_func free_func);

/**
 * 获取下一个索引位置
 */
int queue_get_next(Queue *queue, int current);

/**
 * 队列压人元素
 */
void* queue_push(Queue *queue,pthread_mutex_t *mutex, pthread_cond_t *cond);

/**
 * 弹出元素
 */
void* queue_pop(Queue *queue,pthread_mutex_t *mutex, pthread_cond_t *cond);

队列实现

#include "queue.h"

#include <android/log.h>
#include <pthread.h>

#define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,"ffmpeg",FORMAT,##__VA_ARGS__);

struct _Queue {
    //队列长度
    int size;
    //存放size个的AVPacket **packets;
    void **tab;
    //压入栈元素的下一个元素位置
    int next_to_write;
    //弹出栈元素的下一个元素位置
    int next_to_read;
};

/**
 * 初始化队列
 */
Queue *queue_init(int size, queue_fill_func fill_func){
    Queue* queue = (Queue*)malloc(sizeof(Queue));
    queue->size = size;
    queue->next_to_write = 0;
    queue->next_to_read = 0;
    //数组开辟空间
    queue->tab = (void **) malloc(sizeof(*queue->tab) * size);
    int i;
    for(i=0; i<size; i++){
        queue->tab[i] = fill_func();
    }
    return queue;
}

/**
 * 销毁队列
 */
void queue_free(Queue *queue, queue_free_func free_func){
    int i;
    for(i=0; i<queue->size; i++){
        //销毁队列的元素,通过使用回调函数
        free_func((void*)queue->tab[i]);
    }
    free(queue->tab);
    free(queue);
}

/**
 * 获取下一个索引位置
 */
int queue_get_next(Queue *queue, int current){
    return (current + 1) % queue->size;
}

/**
 * 队列压人元素(生产)
 */
void* queue_push(Queue *queue,pthread_mutex_t *mutex, pthread_cond_t *cond){
    int current = queue->next_to_write;
    int next_to_write;
    for(;;){
        //下一个要读的位置等于下一个要写的,等我写完,在读
        //不等于,就继续
        next_to_write = queue_get_next(queue,current);
        if(next_to_write != queue->next_to_read){
            break;
        }
        //阻塞
        pthread_cond_wait(cond,mutex);
    }

    queue->next_to_write = next_to_write;
    LOGE("queue_push queue:%#x, %d",queue,current);
    //通知
    pthread_cond_broadcast(cond);

    return queue->tab[current];
}

/**
 * 弹出元素(消费)
 */
void* queue_pop(Queue *queue,pthread_mutex_t *mutex, pthread_cond_t *cond){
    int current = queue->next_to_read;
    for(;;){
        if(queue->next_to_read != queue->next_to_write){
            break;
        }
        pthread_cond_wait(cond,mutex);
    }

    queue->next_to_read = queue_get_next(queue,current);
    LOGE("queue_pop queue:%#x, %d",queue,current);

    pthread_cond_broadcast(cond);
    return queue->tab[current];
}

音视频同步

#include <jni.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <android/log.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>

extern "C" {
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include "libyuv/libyuv.h"
}

#include "queue.h"

#define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,"ffmpeg",FORMAT,##__VA_ARGS__);

//队列的大小
#define PACKET_QUEUE_SIZE 50
//16bit 44100 PCM 数据大小
#define MAX_AUDIO_FRME_SIZE 44100 * 2
//视频文件中存在,音频流,视频流,字幕流,这里不测试字幕
#define MAX_STREAM 2

typedef struct _Player Player;
typedef struct _DecoderData DecoderData;

struct _Player {
    //虚拟机
    JavaVM *javaVM;
    //封装格式上下文
    AVFormatContext *input_format_ctx;
    //音频视频流索引位置
    int video_stream_index;
    int audio_stream_index;
    //流的总个数
    int captrue_streams_no;
    //解码器上下文数组
    AVCodecContext *input_codec_ctx[MAX_STREAM];
    //解码线程ID
    pthread_t decode_threads[MAX_STREAM];
    //surface输出窗口
    ANativeWindow *nativeWindow;
    //重采样上下文
    SwrContext *swr_ctx;
    //输入的采样格式
    enum AVSampleFormat in_sample_fmt;
    //输出采样格式16bit PCM
    enum AVSampleFormat out_sample_fmt;
    //输入采样率
    int in_sample_rate;
    //输出采样率
    int out_sample_rate;
    //输出的声道个数
    int out_channel_nb;
    //JNI
    jobject audio_track;
    jmethodID audio_track_write_mid;

    pthread_t thread_read_from_stream;
    //音频,视频队列数组
    Queue *packets[MAX_STREAM];

    //互斥锁
    pthread_mutex_t mutex;
    //条件变量
    pthread_cond_t cond;
};

/**
 * 解码数据
 */
struct _DecoderData {
    Player *player;
    int stream_index;
};

/**
 * 初始化封装格式上下文,获取音频视频流的索引位置
 */
void init_input_format_ctx(Player *player, const char *input_cstr) {
    //1、注册所有组件
    av_register_all();
    //封装格式上下文
    AVFormatContext *format_ctx = avformat_alloc_context();
    //2、打开视频文件
    if (avformat_open_input(&format_ctx, input_cstr, NULL, NULL) != 0) {
        LOGE("Cannot open input file");
        return;
    }
    //3、获取视频信息
    if (avformat_find_stream_info(format_ctx, NULL) < 0) {
        LOGE("Cannot find stream\n");
        return;
    }
    player->captrue_streams_no = format_ctx->nb_streams;
    LOGE("captrue_streams_no:%d", player->captrue_streams_no);
    //4、获取音频和视频流的索引位置
    int i;
    for (i = 0; i < player->captrue_streams_no; i++) {
        if (format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
            player->video_stream_index = i;
        } else if (format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
            player->audio_stream_index = i;
        }
    }
    player->input_format_ctx = format_ctx;
}

/**
 * 初始化解码器上下文
 */
void init_codec_context(Player *player, int stream_idx) {
    AVFormatContext *format_ctx = player->input_format_ctx;
    //获取解码器
    AVCodecContext *codec_ctx = format_ctx->streams[stream_idx]->codec;
    AVCodec *codec = avcodec_find_decoder(codec_ctx->codec_id);
    if (codec == NULL) {
        LOGE("%s", "无法解码");
        return;
    }
    //打开解码器
    if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
        LOGE("%s", "解码器无法打开");
        return;
    }
    player->input_codec_ctx[stream_idx] = codec_ctx;
}

/**
 * 视频解码准备
 */
void decode_video_prepare(JNIEnv *env, Player *player, jobject surface) {
    player->nativeWindow = ANativeWindow_fromSurface(env, surface);
}

/**
 * 音频解码准备
 */
void decode_audio_prepare(Player *player) {
    AVCodecContext *codec_ctx = player->input_codec_ctx[player->audio_stream_index];
    //输入的采样格式
    enum AVSampleFormat in_sample_fmt = codec_ctx->sample_fmt;
    //输出采样格式16bit PCM
    enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
    //输入采样率
    int in_sample_rate = codec_ctx->sample_rate;
    //输出采样率
    int out_sample_rate = in_sample_rate;
    //获取输入的声道布局
    uint64_t in_ch_layout = codec_ctx->channel_layout;
    //输出的声道布局(立体声)
    uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;

    //frame->16bit 44100 PCM 统一音频采样格式与采样率
    SwrContext *swr_ctx = swr_alloc();
    //重采样设置参数
    swr_alloc_set_opts(swr_ctx,
                       out_ch_layout, out_sample_fmt, out_sample_rate,
                       in_ch_layout, in_sample_fmt, in_sample_rate,
                       0, NULL);
    swr_init(swr_ctx);
    //输出的声道个数
    int out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout);

    player->in_sample_fmt = in_sample_fmt;
    player->out_sample_fmt = out_sample_fmt;
    player->in_sample_rate = in_sample_rate;
    player->out_sample_rate = out_sample_rate;
    player->out_channel_nb = out_channel_nb;
    player->swr_ctx = swr_ctx;
}

/**
 * 初始化JNI
 */
void jni_audio_prepare(JNIEnv *env, jobject jthiz, Player *player) {
    //JNI begin------------------
    //JasonPlayer
    jclass player_class = env->GetObjectClass(jthiz);
    //AudioTrack对象
    jmethodID create_audio_track_mid = env->GetMethodID(player_class, "createAudioTrack",
                                                        "(I)Landroid/media/AudioTrack;");
    jobject audio_track = env->CallObjectMethod(jthiz, create_audio_track_mid,
                                                player->out_sample_rate, player->out_channel_nb);
    //调用AudioTrack.play方法
    jclass audio_track_class = env->GetObjectClass(audio_track);
    jmethodID audio_track_play_mid = env->GetMethodID(audio_track_class, "play", "()V");
    env->CallVoidMethod(audio_track, audio_track_play_mid);
    //AudioTrack.write
    jmethodID audio_track_write_mid = env->GetMethodID(audio_track_class, "write", "([BII)I");
    //JNI end------------------
    player->audio_track = env->NewGlobalRef(audio_track);
    //env->DeleteGlobalRef
    player->audio_track_write_mid = audio_track_write_mid;
}

/**
 * 给AVPacket开辟空间,后面会将AVPacket栈内存数据拷贝至这里开辟的空间
 */
void *player_fill_packet() {
    //请参照我在vs中写的代码
    AVPacket *packet = (AVPacket *) malloc(sizeof(AVPacket));
    return packet;
}

/**
 * 初始化音频,视频AVPacket队列,长度50
 */
void player_alloc_queues(Player *player) {
    int i;
    //这里,正常是初始化两个队列
    for (i = 0; i < player->captrue_streams_no; ++i) {
        Queue *queue = queue_init(PACKET_QUEUE_SIZE, (queue_fill_func) player_fill_packet);
        player->packets[i] = queue;
        //打印视频音频队列地址
        LOGE("stream index:%d,queue:%#x", i, queue);
    }
}

/**
 * 生产者线程:负责不断的读取视频文件中AVPacket,分别放入两个队列中
 */
void *player_read_from_stream(void *arg) {
    int index = 0;
    int ret;
    Player *player = (Player *) arg;
    //栈内存上保存一个AVPacket
    AVPacket packet, *pkt = &packet;
    for (;;) {
        ret = av_read_frame(player->input_format_ctx, pkt);
        //到文件结尾了,这里有个bug
        if (ret < 0) {
            sleep(8);
            break;
        }
        //根据AVpacket->stream_index获取对应的队列
        Queue *queue = player->packets[pkt->stream_index];
        //示范队列内存释放
        //queue_free(queue,packet_free_func);
        pthread_mutex_lock(&player->mutex);
        //将AVPacket压入队列
        AVPacket *packet_data = (AVPacket *) queue_push(queue, &player->mutex, &player->cond);
        //拷贝(间接赋值,拷贝结构体数据)
        *packet_data = packet;
        pthread_mutex_unlock(&player->mutex);
        LOGE("queue:%#x, packet:%#x", queue, packet);
    }
}


/**
 * 解码视频
 */
void decode_video(Player *player, AVPacket *packet) {
    //像素数据(解码数据)
    AVFrame *yuv_frame = av_frame_alloc();
    AVFrame *rgb_frame = av_frame_alloc();
    //绘制时的缓冲区
    ANativeWindow_Buffer outBuffer;
    AVCodecContext *codec_ctx = player->input_codec_ctx[player->video_stream_index];
    int got_frame;
    //解码AVPacket->AVFrame
    avcodec_decode_video2(codec_ctx, yuv_frame, &got_frame, packet);
    //Zero if no frame could be decompressed
    //非零,正在解码
    if (got_frame) {
        //lock
        //设置缓冲区的属性(宽、高、像素格式)
        ANativeWindow_setBuffersGeometry(player->nativeWindow, codec_ctx->width, codec_ctx->height,
                                         WINDOW_FORMAT_RGBA_8888);
        ANativeWindow_lock(player->nativeWindow, &outBuffer, NULL);

        //设置rgb_frame的属性(像素格式、宽高)和缓冲区
        //rgb_frame缓冲区与outBuffer.bits是同一块内存
        avpicture_fill((AVPicture *) rgb_frame, (const uint8_t *) outBuffer.bits, AV_PIX_FMT_RGBA,
                       codec_ctx->width, codec_ctx->height);

        //YUV->RGBA_8888
        libyuv::I420ToARGB(yuv_frame->data[0], yuv_frame->linesize[0],
                           yuv_frame->data[2], yuv_frame->linesize[2],
                           yuv_frame->data[1], yuv_frame->linesize[1],
                           rgb_frame->data[0], rgb_frame->linesize[0],
                           codec_ctx->width, codec_ctx->height);

        //unlock
        ANativeWindow_unlockAndPost(player->nativeWindow);
        usleep(1000 * 16);
    }
    av_frame_free(&yuv_frame);
    av_frame_free(&rgb_frame);
}

/**
 * 音频解码
 */
void decode_audio(Player *player, AVPacket *packet) {
    AVCodecContext *codec_ctx = player->input_codec_ctx[player->audio_stream_index];
    LOGE("%s", "decode_audio");
    //解压缩数据
    AVFrame *frame = av_frame_alloc();
    int got_frame;
    avcodec_decode_audio4(codec_ctx, frame, &got_frame, packet);

    //16bit 44100 PCM 数据(重采样缓冲区)
    uint8_t *out_buffer = (uint8_t *) av_malloc(MAX_AUDIO_FRME_SIZE);
    //解码一帧成功
    if (got_frame > 0) {
        swr_convert(player->swr_ctx, &out_buffer, MAX_AUDIO_FRME_SIZE,
                    (const uint8_t **) frame->data, frame->nb_samples);
        //获取sample的size
        int out_buffer_size = av_samples_get_buffer_size(NULL, player->out_channel_nb,
                                                         frame->nb_samples, player->out_sample_fmt,
                                                         1);

        //关联当前线程的JNIEnv
        JavaVM *javaVM = player->javaVM;
        JNIEnv *env;
        javaVM->AttachCurrentThread(&env, NULL);
        //out_buffer缓冲区数据,转成byte数组
        jbyteArray audio_sample_array = env->NewByteArray(out_buffer_size);
        jbyte *sample_bytep = env->GetByteArrayElements(audio_sample_array, NULL);
        //out_buffer的数据复制到sampe_bytep
        memcpy(sample_bytep, out_buffer, out_buffer_size);
        //同步
        env->ReleaseByteArrayElements(audio_sample_array, sample_bytep, 0);

        //AudioTrack.write PCM数据
        env->CallIntMethod(player->audio_track, player->audio_track_write_mid,
                           audio_sample_array, 0, out_buffer_size);
        //释放局部引用
        env->DeleteLocalRef(audio_sample_array);

        javaVM->DetachCurrentThread();

        usleep(1000 * 16);
    }

    av_frame_free(&frame);
}

/**
 * 解码子线程函数(消费)
 */
void *decode_data(void *arg) {
    DecoderData *decoder_data = (DecoderData *) arg;
    Player *player = decoder_data->player;
    int stream_index = decoder_data->stream_index;
    //根据stream_index获取对应的AVPacket队列
    Queue *queue = player->packets[stream_index];

    AVFormatContext *format_ctx = player->input_format_ctx;
    //编码数据

    //6.一阵一阵读取压缩的视频数据AVPacket
    int video_frame_count = 0, audio_frame_count = 0;
    for (;;) {
        //消费AVPacket
        pthread_mutex_lock(&player->mutex);
        AVPacket *packet = (AVPacket *) queue_pop(queue, &player->mutex, &player->cond);
        pthread_mutex_unlock(&player->mutex);
        if (stream_index == player->video_stream_index) {
            decode_video(player, packet);
            LOGE("video_frame_count:%d", video_frame_count++);
        } else if (stream_index == player->audio_stream_index) {
            decode_audio(player, packet);
            LOGE("audio_frame_count:%d", audio_frame_count++);
        }
    }
}

extern "C"
JNIEXPORT void JNICALL
Java_com_handsome_ndkffmpeg_FFmpegUtils_videoAndSoundPlay(JNIEnv *env, jobject instance,
                                                          jstring input_path_, jobject surface) {
    const char *input_path = env->GetStringUTFChars(input_path_, 0);
    Player *player = (Player *) malloc(sizeof(Player));
    env->GetJavaVM(&(player->javaVM));

    //初始化封装格式上下文
    init_input_format_ctx(player, input_path);
    int video_stream_index = player->video_stream_index;
    int audio_stream_index = player->audio_stream_index;
    //获取音视频解码器,并打开
    init_codec_context(player, video_stream_index);
    init_codec_context(player, audio_stream_index);

    //初始化音视频
    decode_video_prepare(env, player, surface);
    decode_audio_prepare(player);

    //初始化JNI
    jni_audio_prepare(env, instance, player);
    //初始化音视频AVPacket队列
    player_alloc_queues(player);

    pthread_mutex_init(&player->mutex, NULL);
    pthread_cond_init(&player->cond, NULL);

    //生产者线程
    pthread_create(&(player->thread_read_from_stream), NULL, player_read_from_stream,
                   (void *) player);
    sleep(1);

    //消费者线程
    DecoderData data1 = {player, video_stream_index}, *decoder_data1 = &data1;
    pthread_create(&(player->decode_threads[video_stream_index]), NULL, decode_data,
                   (void *) decoder_data1);

    DecoderData data2 = {player, audio_stream_index}, *decoder_data2 = &data2;
    pthread_create(&(player->decode_threads[audio_stream_index]), NULL, decode_data,
                   (void *) decoder_data2);

    pthread_join(player->thread_read_from_stream, NULL);
    pthread_join(player->decode_threads[video_stream_index], NULL);
    pthread_join(player->decode_threads[audio_stream_index], NULL);

    env->ReleaseStringUTFChars(input_path_, input_path);
}

源码下载

源码下载

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

NDK开发——FFmpeg实现视频转YUV、视频转RGB显示、音频转PCM、音频播放、音视频同步 的相关文章

  • FFMPEG - 连续的非单调 DTS

    我有几个需要连接的文件 有时文件工作和连接似乎没有问题 然后在其他文件上 文件不会连接 我得到 非单调 DTS 我一直在谷歌上搜索我应该对这些文件进行哪些处理 以便它们正确连接 但我仍然没有找到 有没有办法让所有文件的 DTS 完全相同 我
  • 如何使用 imagemagick 从命令行将灰度 png 图像转换为 RGB

    我正在尝试使用以下命令将 png 灰度图像转换为 RGB png 图像 convert HopeLoveJoy png size 1x1 fill rgba 0 1 2 0 draw color 511 511 point out test
  • 如何在C中将RGB转换为HSL?

    如何在 C C 中将 RGB 转换为 HSL Note This is a short self answer I posted it here so people can find it quickly with a search 翻译代
  • 将专用 NV12 视频帧转换为 RGB

    我有一个使用 Android MediaCodec 解码的 H264 流 当我查询输出 MediaFormat 时 颜色格式为 2141391875 显然 这是一种专门的 NV12 变体 称为 HAL PIXEL FORMAT NV12 A
  • 从 Perl 守护程序运行时,为什么 FFMpeg 在五秒后停止?

    我用 Perl 编写了一个小守护程序 它调用 FFMpeg 对视频进行编码 但编码在 5 秒左右后停止 我用这段代码来启动它 my t echo ffmpeg command gt gt self gt FFMPEG OUTPUT my l
  • pictureBox 图片处理异常

    我最近想尝试一下锻造网 http www aforgenet com framework 因为我发现它非常简单 所以我决定使用 Video FFMPEG 命名空间进行一些简单的视频播放 这样我就可以将每个帧直接放在 pictureBox 上
  • 使用 ffmpeg 在纯色背景上叠加延时视频

    我有很多名为的主页屏幕截图homescreen000001 png homescreen000002 png等 我正在尝试使用 ffmpeg 创建这些图像的延时视频 当我运行以下命令时 它通常可以正常工作 ffmpeg f image2 i
  • ffmpeg通过添加框或边框来扩展(而不是调整大小)视频大小

    假设我有一个特殊分辨率的视频 例如 1280x718 我想将其更改为 1280x720 但我宁愿只在顶部和底部添加一行 而不是将 718 像素垂直插值到 720 所以基本上 我正在寻找一种方法告诉 ffmpeg 创建 1280x720 的输
  • 作为颜色表示的值

    将值转换为颜色是众所周知的 我确实理解以下两种方法 在改变 RGB 颜色值来表示一个值 https stackoverflow com questions 1423925 changing rgb color values to repre
  • 使用 ffmpeg 从 unix 命令批量将 wav 文件转换为 16 位

    我有一个由许多子文件夹组成的文件夹 每个子文件夹都有其他子文件夹 其中包含 wav 文件 我想像这样转换所有文件 ffmpeg i BmBmGG BmBmBmBm wav acodec pcm s16le ar 44100 BmBmGG B
  • Android 上的 GStreamer

    谁能给我一些关于让 GStreamer 在 Android 上工作的提示 我以前从未使用过它 我想将它与 FFmpeg 一起使用 我已经编译了 FFmpeg 并且在 Android 上运行良好 我只是想使用 GStreamer 来帮助完成一
  • 将 H264 视频转换为原始 YUV 格式

    是否可以使用 ffmpeg 从 H264 编码视频创建原始 YUV 视频 我想用 matlab 打开视频并逐帧访问 Luma Cb 和 Cr 分量 是的 您可以 您只需指定像素格式即可 要获取格式的完整列表 ffmpeg pix fmts
  • Google Cloud Platform:将上传的 MP4 文件转换为 HLS 文件

    我正在构建一个平台 允许用户将一些视频文件 20 40 秒 从手机上传到服务器 所有这些上传目前都运行良好 文件通过nodejs云功能存储在谷歌存储桶中 现在我想创建一个 gcp 转码器作业 它将上传的 mp4 视频文件转换为 hls 视频
  • 使用 ffmpeg 提取帧的最快方法?

    您好 我需要使用 ffmpeg 从视频中提取帧 有没有比这更快的方法 ffmpeg i file mpg r 1 1 filename 03d jpg 如果 JPEG 编码步骤对性能要求太高 您可以始终将未压缩的帧存储为 BMP 图像 ff
  • Qt WinRT 应用程序无法访问文件权限被拒绝

    我需要使用 Qt 和 FFMPEG 开发 WinRT 应用程序 我根据指令构建了 WinRT 的 ffmpeghere https github com Microsoft FFmpegInterop我可以将库与我的项目链接起来 现在我需要
  • ffmpeg:音频样本的字节顺序

    我使用 ffmpeg 的 avcodec 从我的 C 应用程序中的音乐文件中检索原始音频样本 对于我测试的文件 这些文件样本的字节序似乎是小字节序 但我想知道对于我尝试解码的所有文件是否总是如此 即来自 ffmpeg 的实现或至少它的体系结
  • 在 ffmpeg 中,如何使用scale2ref 过滤器缩放 dvdsub 字幕以匹配视频大小?

    我有一个从直播电视录制的 mpeg 文件 其中包含视频以及多个音频和字幕流 我的最终目标是能够创建较小的视频文件 因为 mpeg 文件大小为数 GB 我在这条道路上的第一步只是能够选择视频 音频和字幕流中的每一个并将它们复制到 mkv 文件
  • 生成 n 色彩虹调色板

    我正在尝试用 可运行的代码在这里 http sketchpad cc XEXd5II9nC size 360 100 colorMode HSB 360 100 100 Hue in degrees in 0 360 saturation
  • Python 子进程(ffmpeg)仅在我按 Ctrl-C 程序时启动?

    我正在尝试使用 Cygwin 和 Python 2 7 并行运行一些 ffmpeg 命令 这大概是我所拥有的 import subprocess processes set commands ffmpeg i input mp4 outpu
  • 在 MacOS 终端上运行 ffmpeg [关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 我对 MacOS 相当陌生 我发现使用终端来获取信息并不容易ffmpeg和我在 Window 上一样正常运行 我有 ffmpeg 二进制文件ffmpe

随机推荐

  • 使用Python对Excel文件进行处理的小技巧

    Excel文件是一个常用的电子表格文件格式 它可以包含大量的数据和公式 Python有很多库可以用来处理Excel文件 下面介绍其中一些常用的库和技巧 目录 1 使用pandas库读取Excel文件 2 使用openpyxl库进行Excel
  • 内存管理分配,连续分配、分页存储、分段存储、段页式

    目录 1 连续分配管理 1 1 固定分区分配 1 2 动态分区分配 2 非连续分配方式 2 1 基本分页式内存分配管理 2 1 1 基本分页式内存思想与方法 2 1 2 进程逻辑地址与内存物理地址如何转换 2 1 3 页面在内存中的起始地址
  • typescript对与axios的封装

    import Modal message from antd import axios AxiosError AxiosRequestConfig AxiosResponse from axios import AdminConfig fr
  • 一篇搞懂java、javac版本不匹配发生的原因

    问题描述 当我们使用IDE或cmd进行java编译时 有时会发生这样的错误 Error A JNI error has occurred please check your installation and try again 异常部分描述
  • 前端面试的性能优化部分(6)每天10个小知识点

    目录 系列文章目录 前端面试的性能优化部分 1 每天10个小知识点 前端面试的性能优化部分 2 每天10个小知识点 前端面试的性能优化部分 3 每天10个小知识点 前端面试的性能优化部分 4 每天10个小知识点 前端面试的性能优化部分 5
  • sql-libs——less5(盲注)

    首先打开页面 输入 id 1 发现没有任何数据爆出这时把ID换成一个不存在数据 1试一下 发现连 you are in 都没有了 这就是典型的盲注 正确时显示you are in 不正确时什么也不显示 http 192 168 0 5 90
  • Wolfram Mathematica 动手实践

    特点 Mathematica 的介绍 目标是提供介绍 Mathematica 广度的实践经验 重点是易用性 内容 完整概述 基础知识 Mathematica 中的示例项目 输入和输出 文字处理和排版 幻灯片演示 Wolfram 语言基础 使
  • QT信号槽失效的原因

    转载自 https blog csdn net luolaihua2018 article details 110388411 信号槽失效的原因 用Qt进行信号与信号槽连接后 会出现信号槽不响应信号的情况 原因可能是以下的情况 1 类没有声
  • 【MATLAB第12期】基于LSTM长短期记忆网络的多输入多输出回归预测模型思路框架,含滑动窗口, 预测未来,单步预测与多步预测对比,多步预测步数对预测结果影响分析

    MATLAB第12期 基于LSTM RNN作为对比 长短期记忆网络的多输入多输出回归预测模型思路框架 含滑动窗口 预测未来 单步预测与多步预测对比 多步预测步数对预测结果影响分析 更新 2022 11 5更新RNN模型 预测结果附后 一 数
  • 初学(7)——Hadoop错误:can‘t create 事务 lock on /var/lib/rpm/.rpm.lock (权限不够)

    执行删除操作时出现错误 权限不够 1 使用sudo命令 2 如果出现上述情况 切换至root用户 将要执行该操作的用户添加到sudoers su vim etc sudoers xxx 要添加的用户 ALL ALL ALL 命令成功执行
  • Mac用iTerm2连接到Linux上,不能输入中文

    服务器是ubuntu 用Mac的iterm2 ssh连上去 终端显示中文乱码 也不能输入中文 然而本地终端可以显示和输入 解决方法 这种情况一般是终端和服务器的字符集不匹配 MacOSX下默认的是utf8字符集 输入locale可以查看字符
  • 步进电机实验

    通过 ULN2003 驱动模块控制 28BYJ48 步进电机运行方向和速度 按下 KEY1 键调节电机旋转方向 按下 KEY2 键 电机加速 当按下 KEY3 键 电机减速 实现对步进电机运动的简单控制 步进电机简介 步进电机是将电脉冲信号
  • java中io各种流的关闭顺序

    还是先看API void close Closes this stream and releases any system resources associated with it close void close throws IOExc
  • 【react从入门到精通】初识React

    文章目录 人工智能福利文章 前言 React技能树 什么是 React 安装和配置 React 创建 React 组件 渲染 React 组件 使用 JSX 传递属性 Props 处理组件状态 State 处理用户输入 事件处理 组合和嵌套
  • 生成扩散模型漫谈(一):DDPM = 拆楼 + 建楼

    说到生成模型 VAE GAN可谓是 如雷贯耳 本站也有过多次分享 此外 还有一些比较小众的选择 如flow模型 VQ VAE等 也颇有人气 尤其是VQ VAE及其变体VQ GAN 近期已经逐渐发展到 图像的Tokenizer 的地位 用来直
  • Mac环境下安装、配置cocos2d-x环境

    转自 https www jianshu com p 2649a88b7f4d Mac环境下安装 配置cocos2d x环境 玉米包谷关注 2017 09 23 11 16 07字数 207阅读 2 359 1 将cocos2d x下载到桌
  • 学习笔记:进程间通信

    进程间通信的方式 示例 管道的概念及使用 管道pipe详解点击此处 示例 注 写端fd 1 读端fd 0 创建共享内存 mmap函数 思考 总结 示例 mmap父子进程通信 匿名映射 示例 进程1 进程二 shmget函数和mmap的区别
  • 逆矩阵的概念与性质

    逆矩阵的概念与性质 定理1 若矩阵A是可逆的 则A的逆矩阵是唯一的 并记作A的逆矩阵为A 1 性质 定理3 设A为n阶矩阵 则下列各命题等价 1 A是可逆的 2 AX 0只有零解 3 A与I行等价 4 A可表为有限个初等矩阵的乘积 注意 初
  • Docker的概念(1)

    前置条件 需要掌握Linux及常用命令 目录 1 Docker是什么 2 Docker用途 3 Docker与虚拟化 4 Docker和一个正常的虚拟机有何区别 5 Docker虚拟化的好处 5 1 应用部署方便 5 2 服务器同等配置 性
  • NDK开发——FFmpeg实现视频转YUV、视频转RGB显示、音频转PCM、音频播放、音视频同步

    项目演示 前提准备 编译FFmpeg CMake并能运行 详细可见我博客 下载libyuv库并编译成libyuv so库 用于实现转换RGB格式功能 FFmpeg库简介 avcodec 编解码 包含 avformate 封装格式处理 avf