基于FFmpeg,实现播放器功能

在这里插入图片描述

一、客户端选择音视频文件

MainActivity

package com.anniljing.ffmpegnative;

import android.Manifest;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.text.TextUtils;
import android.util.Log;
import android.view.SurfaceHolder;

import com.anniljing.ffmpegnative.databinding.ActivityMainBinding;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import pub.devrel.easypermissions.EasyPermissions;

public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback {
    private static final String TAG = MainActivity.class.getSimpleName();
    private ActivityMainBinding binding;
    private Context mContext;
    private FFmpegPlayer mFFmpegPlayer;
    private String[] PERMISSIONS_STORAGE = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};
    private ActivityResultLauncher<Intent> mResultLauncher;
    private String videoPath;
    private SurfaceHolder mSurfaceHolder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = this;
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        if (!EasyPermissions.hasPermissions(mContext, PERMISSIONS_STORAGE)) {
            EasyPermissions.requestPermissions(MainActivity.this, "", 100, PERMISSIONS_STORAGE);
        }
        mSurfaceHolder = binding.surfaceView.getHolder();
        mSurfaceHolder.addCallback(this);
        mFFmpegPlayer = new FFmpegPlayer();
        mResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
            if (result.getResultCode() == RESULT_OK) {
                Uri data = result.getData().getData();
                Log.e(TAG, "onActivityResult:" + data);
                videoPath = getPathFromContentUri(mContext, data);
                Log.e(TAG, "getPathFromContentUri:" + videoPath);
            }
        });
        binding.play.setOnClickListener((view) -> {
            Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
            intent.addCategory(Intent.CATEGORY_OPENABLE);
            intent.setType("video/*");
            mResultLauncher.launch(intent);
        });
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }

    public static String getPathFromContentUri(Context context, Uri uri) {
        if (uri == null) return null;

        String filePath = null;
        if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
            ContentResolver contentResolver = context.getContentResolver();
            Cursor cursor = contentResolver.query(uri, null, null, null, null);
            if (cursor != null && cursor.moveToFirst()) {
                int columnIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
                if (columnIndex != -1) {
                    String displayName = cursor.getString(columnIndex);
                    if (displayName != null) {
                        filePath = getFilePathFromDisplayName(context, uri, displayName);
                    }
                }
                cursor.close();
            }
        }
        return filePath;
    }

    private static String getFilePathFromDisplayName(Context context, Uri uri, String displayName) {
        String filePath = null;
        ContentResolver contentResolver = context.getContentResolver();
        Uri mediaUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
        Cursor cursor = contentResolver.query(mediaUri, null, MediaStore.Video.Media.DISPLAY_NAME + "=?", new String[]{displayName}, null);
        if (cursor != null && cursor.moveToFirst()) {
            int columnIndex = cursor.getColumnIndex(MediaStore.Video.Media.DATA);
            if (columnIndex != -1) {
                filePath = cursor.getString(columnIndex);
            }
            cursor.close();
        }
        return filePath;
    }

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

    @Override
    public void surfaceCreated(@NonNull SurfaceHolder holder) {
        if (!TextUtils.isEmpty(videoPath)) {
            new Thread(() -> {
                if (mFFmpegPlayer != null) {
                    mFFmpegPlayer.native_start(videoPath, mSurfaceHolder.getSurface());
                }
            }).start();
        }
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder holder) {

    }
}

1.1、访问视频文件目录

   Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
   intent.addCategory(Intent.CATEGORY_OPENABLE);
   intent.setType("video/*");
   mResultLauncher.launch(intent);
 

1.2、解析返回的视频路径

ActivityResultLauncher 是 Android Jetpack 中的一个组件,用于简化处理启动活动并接收结果的过程。它是在 Android API 级别 30(Android 11)引入的新特性,旨在替代过时的 startActivityForResult 方法。

ActivityResultLauncher 使用了一种更简单和类型安全的方式来处理活动结果。它通过注册一个回调并在回调中处理结果,而不需要重写 onActivityResult 方法。这使得代码更加清晰和易于维护。

private ActivityResultLauncher<Intent> mResultLauncher;

mResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback<ActivityResult>() {
            @Override
            public void onActivityResult(ActivityResult result) {
                if (result.getResultCode() == RESULT_OK) {
                    Uri data = result.getData().getData();
                    Log.e(TAG, "onActivityResult:" + data);
                    videoPath = getPathFromContentUri(mContext, data);
                    Log.e(TAG, "getPathFromContentUri:" + videoPath);
                }
            }
        });
public static String getPathFromContentUri(Context context, Uri uri) {
        if (uri == null) return null;

        String filePath = null;
        if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
            ContentResolver contentResolver = context.getContentResolver();
            Cursor cursor = contentResolver.query(uri, null, null, null, null);
            if (cursor != null && cursor.moveToFirst()) {
                int columnIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
                if (columnIndex != -1) {
                    String displayName = cursor.getString(columnIndex);
                    if (displayName != null) {
                        filePath = getFilePathFromDisplayName(context, uri, displayName);
                    }
                }
                cursor.close();
            }
        }
        return filePath;
    }

    private static String getFilePathFromDisplayName(Context context, Uri uri, String displayName) {
        String filePath = null;
        ContentResolver contentResolver = context.getContentResolver();
        Uri mediaUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
        Cursor cursor = contentResolver.query(mediaUri, null, MediaStore.Video.Media.DISPLAY_NAME + "=?", new String[]{displayName}, null);
        if (cursor != null && cursor.moveToFirst()) {
            int columnIndex = cursor.getColumnIndex(MediaStore.Video.Media.DATA);
            if (columnIndex != -1) {
                filePath = cursor.getString(columnIndex);
            }
            cursor.close();
        }
        return filePath;
    }

二、声明native方法

FFmpegPlayer.java

package com.anniljing.ffmpegnative;

import android.view.Surface;

public class FFmpegPlayer {

    static {
        System.loadLibrary("ffmpegnative");
    }

    public native void native_start(String path, Surface surface);
}

三、jni层实现

native-lib.cpp

#include <jni.h>
#include <string>
#include <android/native_window_jni.h>
#include <unistd.h>

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include "libavutil/imgutils.h"
#include "AndroidLog.h"


JNIEXPORT void JNICALL
Java_com_anniljing_ffmpegnative_FFmpegPlayer_native_1start(JNIEnv *env, jobject thiz, jstring path,
                                                           jobject surface) {


    const char *mPath = env->GetStringUTFChars(path, 0);
    //初始化AVFormatContext
    AVFormatContext *avFormatContext = avformat_alloc_context();
    AVDictionary *pDictionary = NULL;
    av_dict_set(&pDictionary, "timeout", "3000000", 0);
    // 打开输入文件
    int ret = avformat_open_input(&avFormatContext, mPath, NULL, &pDictionary);
    if (ret) {
        return;
    }
    // 获取流信息
    avformat_find_stream_info(avFormatContext, NULL);
    // 查找视频流
    int video_stream_index = -1;
    for (int i = 0; i < (avFormatContext->nb_streams); i++) {
        if (avFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_stream_index = i;
            break;
        }
    }
    LOGD("video stream index:%d", video_stream_index);
    AVStream* videoStream = avFormatContext->streams[video_stream_index];
    AVRational timeBase = videoStream->time_base;
    // 计算帧率
    double frameRate = av_q2d(timeBase);
    LOGD("video rate:%f",frameRate);
    // 获取视频流解码器参数
    AVCodecParameters *codecParameters = avFormatContext->streams[video_stream_index]->codecpar;
    // 查找视频流解码器
    AVCodec *avCodecVideo = avcodec_find_decoder(codecParameters->codec_id);
    // 创建解码器上下文
    AVCodecContext *avCodecContext = avcodec_alloc_context3(avCodecVideo);
    // 设置解码器参数
    avcodec_parameters_to_context(avCodecContext, codecParameters);
    // 打开解码器
    avcodec_open2(avCodecContext, avCodecVideo, NULL);

    // 创建图像转换上下文
    SwsContext *swsContext = sws_getContext(avCodecContext->width, avCodecContext->height,
                                            avCodecContext->pix_fmt, avCodecContext->width,
                                            avCodecContext->height, AV_PIX_FMT_RGBA, SWS_BILINEAR,
                                            0, 0, 0);
    // 获取 ANativeWindow 对象
    ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, surface);
    // 设置渲染格式和大小
    ANativeWindow_setBuffersGeometry(nativeWindow, avCodecContext->width, avCodecContext->height,
                                     WINDOW_FORMAT_RGBA_8888);
    // 分配渲染缓冲区
    ANativeWindow_Buffer outBuffer;
    //从视频流读取数据包到avpacket
    AVPacket *avPacketVideo = av_packet_alloc();
    //从音视频文件中读取下一帧
    while (av_read_frame(avFormatContext, avPacketVideo) >= 0) {
        // 将要解码的数据包送入解码器
        avcodec_send_packet(avCodecContext, avPacketVideo);
        AVFrame *avFrameVideo = av_frame_alloc();
        //从解码器内部缓存中提取解码后的音视频帧
        ret = avcodec_receive_frame(avCodecContext, avFrameVideo);
        if (ret == AVERROR(EAGAIN)) {
            continue;
        } else if (ret < 0) {
            break;
        }
        uint8_t *dst_data[4];
        int dst_linesize[4];
        av_image_alloc(dst_data, dst_linesize, avCodecContext->width, avCodecContext->height,
                       AV_PIX_FMT_RGBA, 1);
        // 锁定 Surface 并获取渲染缓冲区
        ANativeWindow_lock(nativeWindow, &outBuffer, NULL);
        // 将解码后的帧转换为目标格式
        sws_scale(swsContext, avFrameVideo->data, avFrameVideo->linesize, 0, avFrameVideo->height,
                  dst_data, dst_linesize);

        //渲染
        uint8_t *first = static_cast<uint8_t *>(outBuffer.bits);
        uint8_t *src_data = dst_data[0];
        int dstStride = outBuffer.stride * 4;
        int src_linesize = dst_linesize[0];
        for (int i = 0; i < outBuffer.height; ++i) {
            memcpy(first + i * dstStride, src_data + i * src_linesize, dstStride);
        }
        // 解锁 Surface
        ANativeWindow_unlockAndPost(nativeWindow);
        sleep(frameRate);
        av_frame_free(&avFrameVideo);
    }
    LOGD("release");
    av_packet_unref(avPacketVideo);
    ANativeWindow_release(nativeWindow);
    sws_freeContext(swsContext);
    avcodec_free_context(&avCodecContext);
    avformat_close_input(&avFormatContext);
    env->ReleaseStringUTFChars(path, mPath);
}

}


3.1、AVFormatContext

AVFormatContext 是 FFmpeg 库中的一个结构体,用于表示音视频封装格式的上下文信息。它包含了音视频流的封装格式、容器级别的参数和状态,以及与输入输出相关的信息。

typedef struct AVFormatContext {
    /**
     * A class for logging and @ref avoptions. Set by avformat_alloc_context().
     * Exports (de)muxer private options if they exist.
     */
    const AVClass *av_class;

    /**
     * 输入容器格式.
     * 用于分流,通过avformat_open_input()设置.
     */
    struct AVInputFormat *iformat;

    /**
     * 输出容器格式。
     *
     * 用于混流,必须在avformat_write_header()调用前设置.
     */
    struct AVOutputFormat *oformat;

    /**
     * Format private data. This is an AVOptions-enabled struct
     * if and only if iformat/oformat.priv_class is not NULL.
     *
     * - muxing: set by avformat_write_header()
     * - demuxing: set by avformat_open_input()
     */
    void *priv_data;

    /**
     * I/O 上下文.
     *
     * - 分流: 在avformat_open_input() 之前设置(用户必须手动释放)或者通过avformat_open_input()
     *		  自动设置.
     * - 混流: 在avformat_write_header()之前设置.用户必须注意及时关闭/释放IO上下文。
     *
     * 不要设置AVFMT_NOFILE标志给iformat/oformat.flags。因为这种情况下,该值为NULL,混/分流器会通
     * 过其它方式处理I/O。
     */
    AVIOContext *pb;

    /* 后面都是流信息 */
    /**
     * 信号流属性标志,AVFMTCTX_*的组合.
     * 通过libavformat设置.
     */
    int ctx_flags;

    /**
     * AVFormatContext.streams中的元素数量,其实就是流的总数.
     *
     * 通过avformat_new_stream()设置, 禁止其它代码修改。
     */
    unsigned int nb_streams;
    /**
     * 媒体中,所有流的列表,新的流由avformat_new_stream()创建。
     *
     * - 分流: 流在avformat_open_input()函数中由libavformat创建。如果AVFMTCTX_NOHEANDER被设置
     *        带ctx_flags中,新的流可能出现在av_read_frame()中。
     * - 混流: 流在avformat_write_header()函数之前被用户创建
     *
     * 在avformat_free_context()函数中,通过libavformat释放。
     */
    AVStream **streams;

    /**
     * 输入输出文件名
     *
     * - 分流: 通过avformat_open_input()设置。
     * - 混流: 在avformat_write_header()调用前,可以被使用者设置。
     */
    char filename[1024];

    /**
     * 组件第一帧的位置,用AV_TIME_BASE分数秒表示。禁止直接设置,由AVStream的值推导而来。
     *
     * - 分流:通过libavformat设置.
     */
    int64_t start_time;

    /**
     * 留的时长,以AV_TIME_BASE分数秒为单位。如果您不知道任何单个流的持续时间,也不设置其中的任何一
     * 个,请仅设置此值。 如果没有设置,该值可以被AVStream推导出来
     *
     * 只用于分流操作,通过libavformat设置。
     */
    int64_t duration;

    /**
     * 总流的比特率以bit/s为单位,如果流不可用,该值为0。如果流文件大小和时长已知,不要直接设置它,
     * FFmpeg会自动计算。
     */
    int64_t bit_rate;

    unsigned int packet_size;
    int max_delay;

    /**
     * 修改分/混流器操作的标志,一个AVFMT_FLAG_*的组合。
     * 在avformat_open_input() / avformat_write_header()调用之前用户自行设置.
     */
    int flags;
#define AVFMT_FLAG_GENPTS       0x0001 ///< Generate missing pts even if it requires parsing future frames.
#define AVFMT_FLAG_IGNIDX       0x0002 ///< Ignore index.
#define AVFMT_FLAG_NONBLOCK     0x0004 ///< Do not block when reading packets from input.
#define AVFMT_FLAG_IGNDTS       0x0008 ///< Ignore DTS on frames that contain both DTS & PTS
#define AVFMT_FLAG_NOFILLIN     0x0010 ///< Do not infer any values from other values, just return what is stored in the container
#define AVFMT_FLAG_NOPARSE      0x0020 ///< Do not use AVParsers, you also must set AVFMT_FLAG_NOFILLIN as the fillin code works on frames and no parsing -> no frames. Also seeking to frames can not work if parsing to find frame boundaries has been disabled
#define AVFMT_FLAG_NOBUFFER     0x0040 ///< Do not buffer frames when possible
#define AVFMT_FLAG_CUSTOM_IO    0x0080 ///< The caller has supplied a custom AVIOContext, don't avio_close() it.
#define AVFMT_FLAG_DISCARD_CORRUPT  0x0100 ///< Discard frames marked corrupted
#define AVFMT_FLAG_FLUSH_PACKETS    0x0200 ///< Flush the AVIOContext every packet.
/**
 * 混流时,尽量避免将随机/不可控的数据写入输出中,包括随机IDs,实时时间戳/日期,混流器版本等等。
 *
 * 该标记主要用于测试
 */
#define AVFMT_FLAG_BITEXACT         0x0400
#define AVFMT_FLAG_MP4A_LATM    0x8000 ///< Enable RTP MP4A-LATM payload
#define AVFMT_FLAG_SORT_DTS    0x10000 ///< try to interleave outputted packets by dts (using this flag can slow demuxing down)
#define AVFMT_FLAG_PRIV_OPT    0x20000 ///< Enable use of private options by delaying codec open (this could be made default once all code is converted)
#if FF_API_LAVF_KEEPSIDE_FLAG
#define AVFMT_FLAG_KEEP_SIDE_DATA 0x40000 ///< Don't merge side data but keep it separate. Deprecated, will be the default.
#endif
#define AVFMT_FLAG_FAST_SEEK   0x80000 ///< Enable fast, but inaccurate seeks for some formats
#define AVFMT_FLAG_SHORTEST   0x100000 ///< Stop muxing when the shortest stream stops.
#define AVFMT_FLAG_AUTO_BSF   0x200000 ///< Wait for packet data before writing a header, and add bitstream filters as requested by the muxer

    /**
     * 从指定容器格式的输入中读取最大数据的大小。
     * 仅用于分流操作,用户可以在avformat_open_input()函数前设置。
     */
    int64_t probesize;

    /**
     * 从指定容器格式的输入中读取的最大数据时长(以AV_TIME_BASE为单位)。
     * 仅用于分流操作,在avformat_find_stream_info()调用前设置。为0时,让avformat自动选择。
     */
    int64_t max_analyze_duration;

    const uint8_t *key;
    int keylen;

    unsigned int nb_programs;
    AVProgram **programs;

    /**
     * 强制视频codec_id.
     * 分流操作: 用户设置。
     */
    enum AVCodecID video_codec_id;

    /**
     * 强制音频codec_id.
     * 分流操作: 用户设置。
     */
    enum AVCodecID audio_codec_id;

    /**
     * 强制字幕codec_id.
     * 分流操作: 用户设置。
     */
    enum AVCodecID subtitle_codec_id;

    /**
     * 每个索引使用的内存最大值(以字节为单位)。
     * 如果索引超出内存限制,项目会被丢弃以保持较小的内存占用。这回导致seeking较慢和不准确(取决于分流
     * 器)
     * 完全内存索引是强制性的分解器将忽略这一点。
     * - 混流操作: 不实用
     * - 分流操作: 由用户设置
     */
    unsigned int max_index_size;

    /**
     * 从设备获取的实时帧缓冲的最大内存大小(以字节为单位)
     */
    unsigned int max_picture_buffer;

    /**
     * AVChapter数组中的章节数量。
     * 混流时,章节信息通畅会写在文件头中,所以nb_chapters应该在写文件头之前被初始化。一些混流器(例如
     * mov、mkv)可以将章节写在预告中。为了在预告中撰写章节,在write_header调用时nb_chapters必须为
     * 并且在write_trailer被调用时为非0数。
     * - 混流操作: 用户设置
     * - 分流操作: libavformat设置
     */
    unsigned int nb_chapters;
    AVChapter **chapters;

    /**
     * 适用于整个文件的元数据。
     *
     * - 分流操作: libavformat在avformat_open_input()函数中设置。
     * - 混流操作: 调用者可以在avformat_write_header()函数调用前设置。
     *
     * 通过libavformat在函数avformat_free_context()中释放。
     */
    AVDictionary *metadata;

    /**
     * 从Unix纪元(1970年1月1日00:00)开始,以真实世界时间开始流的开始时间,以微秒为单位。 即,流在现
     * 实世界被使用的pts=0时间。
     * - 混流操作: 在avformat_write_header()调用前被调用者设置。如果设置为0或AV_NOPTS_VALUE,则
     * 将使用当前时间(wall-time)。
     * - 分流操作: 由libavformat设置. 如果AV_NOPTS_VALUE未知,注意,一定数量的帧被获取后,该值可能
     * 变得已知。
     */
    int64_t start_time_realtime;

    /**
     * 用于确定avformat_find_stream_info()中帧率的帧数。
     * 仅用于分流,在avformat_find_stream_info()调用前由调用者设置
     */
    int fps_probe_size;

    /**
     * 错误识别; 较高的值将检测到更多的错误,但可能会错误检测一些或多或少有效的部分作为错误。 在
     * avformat_open_input()之前由调用方设置的仅用于解分流。
     */
    int error_recognition;

    /**
     * I/O层自定义中断回调函数。
     *
     * 分流操作: avformat_open_input()调用前由用户设置.
     * 混流操作: avformat_write_header()调用前由用户设置(主要用于AVFMT_NOFILE 格式)。如果用它来
     * 打开文件,该回调也会传递给avio_open2().
     */
    AVIOInterruptCB interrupt_callback;

    /**
     * 启用debug标志。
     */
    int debug;
#define FF_FDEBUG_TS        0x0001

    /**
     * Maximum buffering duration for interleaving.
     *
     * To ensure all the streams are interleaved correctly,
     * av_interleaved_write_frame() will wait until it has at least one packet
     * for each stream before actually writing any packets to the output file.
     * When some streams are "sparse" (i.e. there are large gaps between
     * successive packets), this can result in excessive buffering.
     *
     * This field specifies the maximum difference between the timestamps of the
     * first and the last packet in the muxing queue, above which libavformat
     * will output a packet regardless of whether it has queued a packet for all
     * the streams.
     *
     * Muxing only, set by the caller before avformat_write_header().
     */
    int64_t max_interleave_delta;

    /**
     * 允许非标准和实验性扩展
     * @see AVCodecContext.strict_std_compliance
     */
    int strict_std_compliance;

    /**
	 * 供用户检测文件上发生事件的标志。事件处理后,用户必须清除标志。AVFMT_EVENT_FLAG_ *的组合。
     */
    int event_flags;
#define AVFMT_EVENT_FLAG_METADATA_UPDATED 0x0001 ///< The call resulted in updated metadata.

    /**
     * 在等待第一个时间戳时要读取的最大数据包数。仅用于解码。
     */
    int max_ts_probe;

    /**
     * 避免混流过程中的负面时间戳。 AVFMT_AVOID_NEG_TS_ *常量中的任何值。 请注意,这只适用于使用
     * av_interleaved_write_frame。 (interleave_packet_per_dts正在使用中)
     * - 混流: 用户设置
     * - 分流: 不使用
     */
    int avoid_negative_ts;
#define AVFMT_AVOID_NEG_TS_AUTO             -1 ///< Enabled when required by target format
#define AVFMT_AVOID_NEG_TS_MAKE_NON_NEGATIVE 1 ///< Shift timestamps so they are non negative
#define AVFMT_AVOID_NEG_TS_MAKE_ZERO         2 ///< Shift timestamps so that they start at 0

    /**
     * 传输流id。
     * 这将被移入分流器的私有选项。 因此没有API / ABI兼容性
     */
    int ts_id;

    /**
     * 音频预加载以微秒为单位。 请注意,并非所有格式都支持此功能,如果在不支持的情况下使用它,则可能会发
     * 生不可预知的情况。
     * - 编码: 用户设置
     * - 解码: 不使用
     */
    int audio_preload;

    /**
     * 最大块时间(以微秒为单位)。 请注意,并非所有格式都支持此功能,如果在不支持的情况下使用它,则可能
     * 会发生不可预知的情况。
     * - 编码: 用户设置
     * - 解码: 不使用
     */
    int max_chunk_duration;

    /**
     * 最大块大小(以字节为单位)。注意,并非所有格式都支持此功能,如果在不支持的情况下使用它,可能会发
     * 生不可预知的情况。
     * - 编码: 用户设置
     * - 解码: 不使用
     */
    int max_chunk_size;

    /**
     *  强制使用wallclock时间戳作为pts / dts数据包在B帧存在的情况下存在未定义的结果。
     * - 编码: 不使用
     * - 解码: 用户设置
     */
    int use_wallclock_as_timestamps;

    /**
     * avio标志,用于强制使用AVIO_FLAG_DIRECT。
     * - 编码: 不使用
     * - 解码: 用户设置
     */
    int avio_flags;

    /**
     * 时长字段可以通过各种方式进行计算,并且可以使用此字段了解时长是如何计算的。
     * - 编码: 不使用
     * - 解码: 用户读取
     */
    enum AVDurationEstimationMethod duration_estimation_method;

    /**
     * 打开流时跳过初始字节
     * - 编码: 不使用
     * - 解码: 用户设置
     */
    int64_t skip_initial_bytes;

    /**
     * 正确的单个时间戳溢出
     * - 编码: 不使用
     * - 解码: 用户设置
     */
    unsigned int correct_ts_overflow;

    /**
     * 强制seeking到任意帧(即使没有关键帧)
     * - 编码: 不使用
     * - 解码: 用户设置
     */
    int seek2any;

    /**
     * 在每个数据包之后刷新I / O上下文。
     * - 编码: 用户设置
     * - 解码: 不使用
     */
    int flush_packets;

    /**
     * 格式探测分数。 最高分是AVPROBE_SCORE_MAX,当分流器探测格式时设置它。
     * - 编码: 不使用
     * - 解码: avformat设置,用户读取
     */
    int probe_score;
    /**
     * 要最大限度地读取以识别格式的字节数。
     * - 编码: 不使用
     * - 解码: 用户设置
     */
    int format_probesize;
    /**
     * ',' 分割的支持的解码器刘表,如果值为NULL,表示支持所有解码器。
     * - 编码: 不使用
     * - 解码: 用户设置
     */
    char *codec_whitelist;
     /**
      * ',' 分割的支持的分流器列表,如果值为NULL,所有分流器都支持。
      * - 编码: 不使用
      * - 解码: 用户设置
      */
     char *format_whitelist;
 
     /**
 	  * libavformat内部使用的不透明字段。 不得以任何方式访问。
      */
     AVFormatInternal *internal;
 
     /**
      * IO重定位标志。
      * 当基础IO上下文读指针重新定位时,例如在执行基于字节的查找时,这由avformat设置。 分流器可以使用
      * 标志来检测这种变化。
      */
     int io_repositioned;
 
     /**
      * 强制视频解码器。强制数据解码器。这允许使用强制指定的解码器,即使有多个相同的codec_id.
      * 分流: 用户设置
      */
     AVCodec *video_codec;
 
     /**
      * 强制音频解码器。强制数据解码器。这允许使用强制指定的解码器,即使有多个相同的codec_id.
      * 分流: 用户设置
      */
     AVCodec *audio_codec;
 
     /**
      * 强制数据解码器。这允许使用强制指定的解码器,即使有多个相同的codec_id.
      * 分流: 用户设置
      */
     AVCodec *subtitle_codec;
 
     /**
      * 强制数据解码器。这允许使用强制指定的解码器,即使有多个相同的codec_id.
      * 分流: 用户设置
      */
     AVCodec *data_codec;
 
     /**
      * 在原数据头中,充当填充(分割)的字节数
      * 分流: 不使用
      * 混流: 用户可以通过av_format_set_metadata_header_padding设置.
      */
     int metadata_header_padding;
 
     /**
      * 用户数据,这是用户的私有数据空间。
      */
     void *opaque;
 
     /**
      * 设备用应用通讯的回调。
      */
     av_format_control_message control_message_cb;
 
     /**
      * 输出时移,以微妙为单位。
      * 混流: 用户设置
      */
     int64_t output_ts_offset;
 
     /**
      * 转储格式分隔符。可以是", " 或者 "\n" 等
      * - 混流: 用户设置
      * - 分流: 用户设置
      */
     uint8_t *dump_separator;
 
     /**
      * 强制数据codec_id.
      * 分流操作: 用户设置
      */
     enum AVCodecID data_codec_id;
 
 #if FF_API_OLD_OPEN_CALLBACKS
     /**
      * Called to open further IO contexts when needed for demuxing.
      *
      * This can be set by the user application to perform security checks on
      * the URLs before opening them.
      * The function should behave like avio_open2(), AVFormatContext is provided
      * as contextual information and to reach AVFormatContext.opaque.
      *
      * If NULL then some simple checks are used together with avio_open2().
      *
      * Must not be accessed directly from outside avformat.
      * @See av_format_set_open_cb()
      *
      * Demuxing: Set by user.
      *
      * @deprecated Use io_open and io_close.
      */
     attribute_deprecated
     int (*open_cb)(struct AVFormatContext *s, AVIOContext **p, const char *url, int flags, const AVIOInterruptCB *int_cb, AVDictionary **options);
 #endif
 
     /**
      * ',' 符号分割的支持协议列表separated list of allowed protocols.
      * - 编码: 不使用
      * - 解码: 用户设置
      */
     char *protocol_whitelist;
 
     /*
      * A callback for opening new IO streams.
      *
      * Whenever a muxer or a demuxer needs to open an IO stream (typically from
      * avformat_open_input() for demuxers, but for certain formats can happen at
      * other times as well), it will call this callback to obtain an IO context.
      *
      * @param s the format context
      * @param pb on success, the newly opened IO context should be returned here
      * @param url the url to open
      * @param flags a combination of AVIO_FLAG_*
      * @param options a dictionary of additional options, with the same
      *                semantics as in avio_open2()
      * @return 0 on success, a negative AVERROR code on failure
      *
      * @note Certain muxers and demuxers do nesting, i.e. they open one or more
      * additional internal format contexts. Thus the AVFormatContext pointer
      * passed to this callback may be different from the one facing the caller.
      * It will, however, have the same 'opaque' field.
      */
     int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url,
                    int flags, AVDictionary **options);
 
     /**
      * 将AVFormateContext.io_open()打开流关闭的回调。
      */
     void (*io_close)(struct AVFormatContext *s, AVIOContext *pb);
 
     /**
      * ',' 符分割的不支持协议列表。
      * - 编码: 不使用
      * - 解码: 用户设置
      */
     char *protocol_blacklist;
 
     /**
      * 最大streams数量
      * - 编码: 不使用
      * - 解码: 用户设定
      */
     int max_streams;
 } AVFormatContext;



3.2、AVCodecContext

AVCodecContext 是 FFmpeg 库中的一个结构体,用于描述音视频编解码器的上下文信息。它包含了音视频编解码器的参数和状态,用于配置和控制编解码的过程。


typedef struct AVCodec {
    /**
     * 编解码器实现的名称。
     * 该名称是全局唯一的(但编码器和解码器可以共享名称)。
     * 这是从用户角度查找编解码器的主要方式。
     */
    const char *name;
    /**
     * 编解码器的描述性名称,比前面的名称更具可读性。
     * 您应该使用NULL_IF_CONFIG_SMALL()宏来定义它。
     */
    const char *long_name;
    enum AVMediaType type;//编解码器类型,视频,音频,或者字幕
    enum AVCodecID id;//全局唯一的编解码器ID
    /**
     * Codec capabilities.
     * see AV_CODEC_CAP_*
     */
    int capabilities;
    const AVRational *supported_framerates; ///支持帧率的数组,用于视频
    const enum AVPixelFormat *pix_fmts;     ///< 支持的像素格式数组,或者如果未知,则为NULL,数组以-1结尾。用于视频
    const int *supported_samplerates;       ///< 支持的音频采样率数组,或者如果未知,则为NULL,数组以0结尾。用于音频
    const enum AVSampleFormat *sample_fmts; ///<支持的采样数组,或者如果未知,则为NULL,数组以-1结尾。用于音频
    const uint64_t *channel_layouts;         ///< 支持声道数组,如果未知,则为NULL。 数组以0结尾,用于音频
    uint8_t max_lowres;                     ///< maximum value for lowres supported by the decoder
    const AVClass *priv_class;              ///< 私有上下文的AVClass
    const AVProfile *profiles;              ///< 已识别配置文件的数组,或者如果未知,则为NULL,数组以{FF_PROFILE_UNKNOWN}结尾

    /*****************************************************************
     * 以下所有的字段都不是公共API,不可在libavcodec以外使用。以后新增字段都会放在上面。
     *****************************************************************
     */
    int priv_data_size;//私有数据大小
    struct AVCodec *next;
    /**
     * @name Frame-level threading support functions
     * @{
     */
    /**
     * 如果已定义,则在创建线程上下文时调用它们。
     * 如果编解码器在init()中分配可写表,请在此处重新分配它们。
     * priv_data将被设置为原件的副本。
     */
    int (*init_thread_copy)(AVCodecContext *);
    /**
     * Copy necessary context variables from a previous thread context to the current one.
     * If not defined, the next thread will start automatically; otherwise, the codec
     * must call ff_thread_finish_setup().
     *
     * dst and src will (rarely) point to the same context, in which case memcpy should be skipped.
     */
    int (*update_thread_context)(AVCodecContext *dst, const AVCodecContext *src);
    /** @} */

    /**
     * 私有编解码器默认值。
     */
    const AVCodecDefault *defaults;

    /**
     * 初始化时从avcodec_register()调用的编解码器静态数据。
     */
    void (*init_static_data)(struct AVCodec *codec);

    int (*init)(AVCodecContext *);
    int (*encode_sub)(AVCodecContext *, uint8_t *buf, int buf_size,
                      const struct AVSubtitle *sub);
    /**
     * Encode data to an AVPacket.
     *
     * @param      avctx          codec context
     * @param      avpkt          output AVPacket (may contain a user-provided buffer)
     * @param[in]  frame          AVFrame containing the raw data to be encoded
     * @param[out] got_packet_ptr encoder sets to 0 or 1 to indicate that a
     *                            non-empty packet was returned in avpkt.
     * @return 0 on success, negative error code on failure
     */
    int (*encode2)(AVCodecContext *avctx, AVPacket *avpkt, const AVFrame *frame,
                   int *got_packet_ptr);
    int (*decode)(AVCodecContext *, void *outdata, int *outdata_size, AVPacket *avpkt);
    int (*close)(AVCodecContext *);
    /**
     * Decode/encode API with decoupled packet/frame dataflow. The API is the
     * same as the avcodec_ prefixed APIs (avcodec_send_frame() etc.), except
     * that:
     * - never called if the codec is closed or the wrong type,
     * - AVPacket parameter change side data is applied right before calling
     *   AVCodec->send_packet,
     * - if AV_CODEC_CAP_DELAY is not set, drain packets or frames are never sent,
     * - only one drain packet is ever passed down (until the next flush()),
     * - a drain AVPacket is always NULL (no need to check for avpkt->size).
     */
    int (*send_frame)(AVCodecContext *avctx, const AVFrame *frame);
    int (*send_packet)(AVCodecContext *avctx, const AVPacket *avpkt);
    int (*receive_frame)(AVCodecContext *avctx, AVFrame *frame);
    int (*receive_packet)(AVCodecContext *avctx, AVPacket *avpkt);
    /**
     * Flush buffers.
     * Will be called when seeking
     */
    void (*flush)(AVCodecContext *);
    /**
     * Internal codec capabilities.
     * See FF_CODEC_CAP_* in internal.h
     */
    int caps_internal;
} AVCodec;

3.3、AVPacket

AVPacket 是 FFmpeg 库中的一个结构体,用于存储音视频数据的压缩数据包。它是 FFmpeg 中处理音视频数据的重要数据结构之一。

AVPacket 结构体用于存储音视频数据的压缩数据,例如从容器中读取的音视频帧,或者编码后的音视频帧待写入容器。它包含了数据的时间戳、大小和相关信息,用于解码和编码过程中的数据处理。

在使用 AVPacket 时,可以通过 FFmpeg 的函数和接口进行创建、释放、填充数据等操作。例如,av_packet_alloc 函数用于分配一个新的 AVPacket 对象,av_packet_free 函数用于释放 AVPacket 对象,av_packet_ref 函数用于创建 AVPacket 的副本,av_packet_rescale_ts 函数用于对时间戳进行重新缩放等。

typedef struct AVPacket {
    AVBufferRef *buf; // data的buffer引用指针计数结构体
    int64_t pts; // 控制显示的pts时间
    int64_t dts; // 控制解码的dts时间
    uint8_t *data; // 媒体数据buffer的指针
    int   size; // 数据大小
    int   stream_index; // 流index
    int   flags; // AV_PKT_FLAG值的组合
    AVPacketSideData *side_data; // 容器可以提供的附加数据包数据。 数据包可以包含几种类型的辅助信息。
		// AVStream-> time_base单位中此数据包的持续时间,如果未知则为0。 在演示顺序中等于next_pts  -  this_pts。
    int64_t duration;
    int64_t pos; // 流中的字节位置,如果未知则为-1
} AVPacket;

3.4、AVFrame

AVFrame 是 FFmpeg 库中的一个结构体,用于表示音视频帧的数据。它包含了一个音频帧或视频帧的各种信息,如像素数据、采样数据、时间戳等。


    typedef struct AVStream {
        int index;    /**< 在AVFormatContext中的stream索引 */
        /**
         * 特定格式的stream id。
         * 解码: 由libavformat设定
         * 编码: 如果未设置,则由用户通过libavformat设置
         */
        int id;
    #if FF_API_LAVF_AVCTX
        /**
         * @deprecated use the codecpar struct instead
         */
        attribute_deprecated
        AVCodecContext *codec;
    #endif
        void *priv_data;
    
    #if FF_API_LAVF_FRAC
        /**
         * @deprecated this field is unused
         */
        attribute_deprecated
        struct AVFrac pts;
    #endif
    
        /**
         * 这是表示帧时间戳的基本时间单位(以秒为单位)。
         *
         * 解码: libavformat设置
         * 编码: 可以在avformat_write_header()之前由调用者设置,以向混流器提供关于所需单位时间的
         * 提示。在avformat_write_header()中,混流器将用实际用于写入文件的时间戳(根据格式可能与
         * 用户提供的时间戳相关或不相关)的单位时间覆盖该字段。
         */
        AVRational time_base;
    
        /**
         * 解码: 流显示序列中的第一帧pts时间,基于流时间(in stream time base.)。
         * 只有当你百分百确定该值就是真实的第一帧的pts时间,才可以设置它
         * 该值可能未定义(AV_NOPTS_VALUE).
         * @note The ASF header does NOT contain a correct start_time the ASF
         * 分流器禁止设置该值。
         */
        int64_t start_time;
    
        /**
         * 解码: 流时长,基于流时间(in stream time base.)
         *      如果一个源文件指定了比特率,而未指定流时长,该值将由比特率和文件大小估算。
         *
         * 编码: May be set by the caller before 用户可以在avformat_write_header()调用前设
         * 置,提示混流器估算时长
         */
        int64_t duration;
    
        int64_t nb_frames;                 ///< 表示该流的已知帧数,或者为0
    
        int disposition; /**< AV_DISPOSITION_* 推荐比特字段 */
    
        enum AVDiscard discard; ///< 选择那些数据包可以被丢掉而不用被分流器分流。
    
        /**
         * 采样率(如果未知,该值为0)
         * - 编码: 用户设置.
         * - 解码: libavformat设置.
         */
        AVRational sample_aspect_ratio;
    
        AVDictionary *metadata;//原数据信息
    
        /**
         * 平均帧率
         *
         * - 分流: 在创建流时或者才函数avformat_find_stream_info()函数中可能被设置。
         * - 混流: 可能在avformat_write_header()函数调用前被设置
         */
        AVRational avg_frame_rate;
    
        /**
         * 对于设置有AV_DISPOSITION_ATTACHED_PIC标志的流, 该数据包会包含该附加图片(专辑图片什么的)
         *
         * 解码: libavformat设置, 不能被用户修改。
         * 编码: 不使用
         */
        AVPacket attached_pic;
    
        /**
         * An array of side data that applies to the whole stream (i.e. the
         * container does not allow it to change between packets).
         *
         * There may be no overlap between the side data in this array and side data
         * in the packets. I.e. a given side data is either exported by the muxer
         * (demuxing) / set by the caller (muxing) in this array, then it never
         * appears in the packets, or the side data is exported / sent through
         * the packets (always in the first packet where the value becomes known or
         * changes), then it does not appear in this array.
         *
         * - demuxing: Set by libavformat when the stream is created.
         * - muxing: May be set by the caller before avformat_write_header().
         *
         * Freed by libavformat in avformat_free_context().
         *
         * @see av_format_inject_global_side_data()
         */
        AVPacketSideData *side_data;
        /**
         * The number of elements in the AVStream.side_data array.
         */
        int            nb_side_data;
    
        /**
         * 供用户检测流上发生的时间标志。 事件处理后,用户必须清除标志。 AVSTREAM_EVENT_FLAG_ *的组合。
         */
        int event_flags;
    #define AVSTREAM_EVENT_FLAG_METADATA_UPDATED 0x0001 ///< The call resulted in updated metadata.
    
        /*****************************************************************
      	 *该行下面的所有字段不是公共API的一部分。 它们不能在libavformat之外使用,并且可以随意更改和删
      	 *除。内部提示:请注意,物理删除这些字段将会破坏ABI。 用空字段替换已删除的字段,并向
      	 *AVStreamInternal添加新字段。
         *****************************************************************
         */
    
        /**
         * avformat_find_stream_info()函数使用的内部流信息
         */
    #define MAX_STD_TIMEBASES (30*12+30+3+6)
        struct {
            int64_t last_dts;
            int64_t duration_gcd;
            int duration_count;
            int64_t rfps_duration_sum;
            double (*duration_error)[2][MAX_STD_TIMEBASES];
            int64_t codec_info_duration;
            int64_t codec_info_duration_fields;
    
            /**
             * 0  -> 解码器还未被检索到
             * >0 -> 解码器已被找到
             * <0 -> decoder with codec_id == -found_decoder has not been found
             */
            int found_decoder;
    
            int64_t last_duration;
    
            /**
             * 这些字段用于估算平均帧率
             */
            int64_t fps_first_dts;
            int     fps_first_dts_idx;
            int64_t fps_last_dts;
            int     fps_last_dts_idx;
    
        } *info;
    
        int pts_wrap_bits; /**< number of bits in pts (used for wrapping control) */
    
        // 时间戳生成支持:
        /**
         * 最后同步点的时间戳。
         *
         * 当AVCodecParserContext.dts_sync_point >= 0 时初始化,并且接受一个当前容器的DTS。否
         * 则,AV_NOPTS_VALUE使用默认值
         */
        int64_t first_dts;
        int64_t cur_dts;
        int64_t last_IP_pts;
        int last_IP_duration;
    
        /**
         * 编解码器探测缓存的数据包数量
         */
        int probe_packets;
    
        /**
         * avformat_find_stream_info()调用期间,已经被分流的帧数
         */
        int codec_info_nb_frames;
    
        /* av_read_frame() 支持 */
        enum AVStreamParseType need_parsing;
        struct AVCodecParserContext *parser;
    
        /**
         * 正在混流操作的流在数据包缓冲中的最后一个数据包
         */
        struct AVPacketList *last_in_packet_buffer;
        AVProbeData probe_data;
    #define MAX_REORDER_DELAY 16
        int64_t pts_buffer[MAX_REORDER_DELAY+1];
    
        AVIndexEntry *index_entries; /**< 只有当格式不支持本地seeking时使用*/
        int nb_index_entries;
        unsigned int index_entries_allocated_size;
    
        /**
         * 流的真实基准帧率.
         * 这是所有时间戳可以准确表示的最低帧速率(它是流中所有帧速率的最小公倍数)。 请注意,这个值只
         * 是一个猜测! 例如,如果时基为1/90000,并且所有帧都具有约3600或1800个计时器滴答,则
         * r_frame_rate将为50/1。
         *
         * avformat以外的代码应该使用此字段访问:
         * av_stream_get/set_r_frame_rate(stream)
         */
        AVRational r_frame_rate;
    
        /**
         * 流标志符
         * 这是MPEG-TS流标识符 +1
         * 0 意味着未知
         */
        int stream_identifier;
    
        int64_t interleaver_chunk_size;
        int64_t interleaver_chunk_duration;
    
        /**
         * 流探测状态
         * -1   -> 探测完毕
         *  0   -> 没有探测请求
         * rest -> 以request_probe作为接受的最低分数执行探测。
         * 不是公共API的一部分
         */
        int request_probe;
        /**
         * 表示直到下一个关键帧的所有内容都应该丢弃。
         */
        int skip_to_keyframe;
    
        /**
         * 在下一个数据包解码的帧开始时跳过的采样数。
         */
        int skip_samples;
    
        /**
         * 如果不是0,则应从流的开始位置跳过的样本数量(样本从pts == 0的包中移除,这也假定负时间戳不会
         * 发生)。 旨在用于具有ad-hoc无间断音频支持的mp3等格式。
         */
        int64_t start_skip_samples;
    
        /**
         * 如果不是0,应该从流中丢弃的第一个音频采样。 这是由设计丢弃的(需要全球采样计数),但无法避免
         * 由设计格式(如带有ad-hoc无间隙音频支持的mp3)破坏。
         */
        int64_t first_discard_sample;
    
        /**
         * 在first_discard_sample之后打算丢弃的最后一个样本之后的样本。 仅适用于框架边界。 用于防止
         * 早期EOF,如果无间隙信息被破坏(考虑连接的MP3)。
         */
        int64_t last_discard_sample;
    
        /**
         * 在libavformat内部使用的内部解码帧的数量不会访问其生存期,这与信息不同,因此它不在该结构
         * 中。
         */
        int nb_decoded_frames;
    
        /**
         * 时间戳偏移添加到混流之前的时间戳
         * 非公共API
         */
        int64_t mux_ts_offset;
    
        /**
         * 内部数据检查时间戳的包装
         */
        int64_t pts_wrap_reference;
    
        /**
         * Options for behavior, when a wrap is detected.
         *
         * Defined by AV_PTS_WRAP_ values.
         *
         * If correction is enabled, there are two possibilities:
         * If the first time stamp is near the wrap point, the wrap offset
         * will be subtracted, which will create negative time stamps.
         * Otherwise the offset will be added.
         */
        int pts_wrap_behavior;
    
        /**
         * 禁止执行两次update_initial_durations()的内部数据
         */
        int update_initial_durations_done;
    
        /**
         * 内部数据,用于从pts生成dts
         */
        int64_t pts_reorder_error[MAX_REORDER_DELAY+1];
        uint8_t pts_reorder_error_count[MAX_REORDER_DELAY+1];
    
        /**
         * 内部数据,用于分析DTS和检测错误的MPEG流
         */
        int64_t last_dts_for_order_check;
        uint8_t dts_ordered;
        uint8_t dts_misordered;
    
        /**
         * Internal data to inject global side data
         */
        int inject_global_side_data;
    
        /*****************************************************************
         * 该行上方的所有字段都不是公共API的一部分。 下面的字段是公共API和ABI的一部分。
         *****************************************************************
         */
    
        /**
         * 包含键和值的一系列字符串,用于描述推荐的编码器配置。
         * 系列以 ','分割.
         * 键和值由'='分割.
         */
        char *recommended_encoder_configuration;
    
        /**
         * 显示宽高比(如果未知,则为0)
         * - 编码: 不使用
         * - 解码: libavformat设置, 用于在内部计算显示宽高比。
         */
        AVRational display_aspect_ratio;
    
        struct FFFrac *priv_pts;
    
        /**
         * libavformat内部使用的不透明字段。 不得以任何方式访问。
         */
        AVStreamInternal *internal;
    
        /*
         * 与此流关联的编解码器参数。 分别在avformat_new_stream()和avformat_free_context()
         * 中由libavformat分配和释放。
         *
         * - 分流: 由libavformat在流创建时填充或在avformat_find_stream_info()赋值。
         * - 混流: 在avformat_write_header()之前由调用者填充
         */
        AVCodecParameters *codecpar;
    } AVStream;


3.5、SwsContext

SwsContext 是 FFmpeg 库中的一个结构体,用于进行图像的缩放、颜色空间转换等操作。它是 FFmpeg 中的图像转换模块(swscale)的上下文。

SwsContext 结构体用于图像转换,适用于将一个像素格式的图像转换为另一个像素格式的图像。它可以用于图像缩放、色彩空间转换、图像格式转换等操作。

在 FFmpeg 4.0 版本及以后的版本中,SwsContext 结构体已经被弃用,取而代之的是 sws_getCachedContext 函数。该函数返回一个 struct SwsContext* 类型的指针,用于进行图像的缩放、颜色空间转换等操作。

struct SwsContext *sws_getCachedContext(struct SwsContext *context,
    int srcW, int srcH, enum AVPixelFormat srcFormat,
    int dstW, int dstH, enum AVPixelFormat dstFormat,
    int flags, SwsFilter *srcFilter, SwsFilter *dstFilter, const double *param);
  • int srcW:源图像的宽度。
  • int srcH:源图像的高度。
  • enum AVPixelFormat srcFormat:源图像的像素格式。
  • int dstW:目标图像的宽度。
  • int dstH:目标图像的高度。
  • enum AVPixelFormat dstFormat:目标图像的像素格式。
  • struct SwsFilter *srcFilter:源图像的过滤器。
  • struct SwsFilter *dstFilter:目标图像的过滤器。
  • float *param:用于可选参数的数组。
  • int paramCount:可选参数的数量。

使用 SwsContext 结构体进行图像转换的一般流程如下:
1、创建 SwsContext 对象:使用 sws_getContext 函数创建 SwsContext 对象,需要指定源图像的宽度、高度、像素格式,以及目标图像的宽度、高度、像素格式等参数。

SwsContext *sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat,
                           int dstW, int dstH, enum AVPixelFormat dstFormat,
                           int flags, SwsFilter *srcFilter, SwsFilter *dstFilter, const double *param);

2、进行图像转换:使用 sws_scale 函数进行图像的实际转换,将源图像数据转换为目标图像数据。

int sws_scale(SwsContext *c, const uint8_t *const srcSlice[],
              const int srcStride[], int srcSliceY, int srcSliceH,
              uint8_t *const dst[], const int dstStride[]);

3、释放 SwsContext 对象:使用 sws_freeContext 函数释放 SwsContext 对象,释放相关资源。

void sws_freeContext(SwsContext *swsContext);

参考链接

https://blog.csdn.net/leixiaohua1020/article/details/14215833

https://blog.csdn.net/qq_25333681/category_7686458.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/242057.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

sentinel整合nacos配置中心持久化

在网上找了很多的资料&#xff0c;发现sentinel整合nacos持久化的博文和视频大多数都只有改造限流部分的教程&#xff0c;并且都需要修改前端&#xff0c;略显麻烦&#xff0c;至于剩下的熔断、热点流控、授权的更是没有相关的改造教程&#xff0c;最后在知乎的看到一篇文章后让…

表单参数绑定(如何解决一个输入框绑定两个参数)

表单参数绑定(如何解决一个输入框绑定两个参数) 问题复现 <el-form-item label"会议编号" prop"meetingNum"><el-select v-model"form.meetingNum" placeholder"请选择会议编号" style"width: 100%;":disabled&quo…

selenium+python自动化测试 —— 解决无法启动IE浏览器及报错问题!

前言&#xff1a;记录启动IE浏览器的报错及解决方法。 错误1&#xff1a; selenium.common.exceptions.WebDriverException: Message: IEDriverServer.exe executable needs to be in PATH. Please download from http://selenium-release.storage.googleapis.com/index.html…

SQL Server 服务启动报错:错误1069:由于登录失败而无法启动服务

现象 服务器异常关机以后&#xff0c;SQL Server服务无法启动了。 启动服务时报错&#xff1a; 错误1069:由于登录失败而无法启动服务 解决办法 我的电脑–控制面板–管理工具–服务–右键MSSQLSERVER–属性–登录–登陆身份–选择"本地系统帐户" 设置完成后&am…

springboot整合日志框架log4j2

springboot整合日志框架log4j2 前言&#xff1a;springboot提供了默认的日志框架logback&#xff0c;结合slf4j门面&#xff0c;基于简单配置即可实现日志输出记录。但是实际开发中很多项目会使用log4j2&#xff0c;log4j2是log4j的升级版本&#xff0c;性能和安全性上比log4j…

【视觉SLAM十四讲学习笔记】第四讲——指数映射

专栏系列文章如下&#xff1a; 【视觉SLAM十四讲学习笔记】第一讲——SLAM介绍 【视觉SLAM十四讲学习笔记】第二讲——初识SLAM 【视觉SLAM十四讲学习笔记】第三讲——旋转矩阵 【视觉SLAM十四讲学习笔记】第三讲——旋转向量和欧拉角 【视觉SLAM十四讲学习笔记】第三讲——四元…

ffmpeg编解码——数据包(packet)概念(如何正确处理数据包中的显示时间戳pts与解码时间戳dts关系?)

文章目录 FFmpeg编解码——数据包&#xff08;Packet&#xff09;概念1. 数据包&#xff08;Packet&#xff09;简介2. 数据包&#xff08;Packet&#xff09;在FFmpeg中的应用2.1 从媒体文件读取数据包2.2 向媒体文件写入数据包 3. 数据包&#xff08;Packet&#xff09;相关问…

C语言指针基础题(二)

目录 例题一题目解析及答案 例题二题目解析及答案 例题三题目解析及答案 例题四题目解析及答案 例题五题目解析及答案 感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接 &#x1f412;&#x1f412;&#x1f412; 个人主页 &#x1f978;&#x1f978;&#x1f…

使用Microsoft Dynamics AX 2012 - 8. 财务管理

财务管理的主要职责是控制和分析与货币金额有关的所有交易。这些事务发生在整个组织的业务流程中。 因此&#xff0c;财务管理是企业管理解决方案的核心领域。在Dynamics AX中&#xff0c;支持所有部门业务流程的应用程序的深度集成可立即提供准确的财务数据。 分类账交易的原…

SQL小技巧3:分层汇总

前几天&#xff0c;QQ学习群有个小伙伴问我一个使用SQL分层汇总的问题。 今天正好分享下。 需求描述 在数据报表开发的工作中&#xff0c;经常会遇到需要对数据进行分组汇总的情况。 假设有一个销售数据表sales&#xff0c;包含列region&#xff08;地区&#xff09;、mont…

验收支撑-软件项目验收计划书

软件项目验收计划的作用主要有以下几点&#xff1a; 确保项目质量&#xff1a;通过项目验收&#xff0c;客户或相关方可以对项目的成果进行全面、系统的评估&#xff0c;以确保项目达到预期的质量标准。发现和解决问题&#xff1a;在项目开发过程中&#xff0c;难免会存在一些问…

SpringBoot程序打包失败处理

&#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;程序员老茶 &#x1f64a; ps:点赞&#x1f44d;是免费的&#xff0c;却可以让写博客的作者开心好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#xff0c;…

产品入门第三讲:Axure产品流程图绘制

&#x1f4da;&#x1f4da; &#x1f3c5;我是默&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; ​​​​​ &#x1f31f;在这里&#xff0c;我要推荐给大家我的专栏《Axure》。&#x1f3af;&#x1f3af; &#x1f680;无论你是编程小白&#xff0c;还…

AE (3)_主观亮度和对比度调试

#灵感# 画面的亮度、对比度是最直接&#xff0c;观看者最先获得的感受。所以有必要花时间认真调整。 ------哈哈&#xff0c;虽然调试的时间其实不如磨清晰度多。 目录 举个图例&#xff1a; 三个参数AE、gamma、LTM&#xff1a; 调试顺序&#xff1a; 举个图例&#xff1…

【Hadoop_06】MapReduce的概述与wc案例

1、MapReduce概述1.1 MapReduce定义1.2 MapReduce优点1.3 MapReduce缺点1.4 MapReduce核心思想1.5 MapReduce进程1.6 常用数据序列化类型1.7 源码与MapReduce编程规范 2、WordCount案例实操2.1 本地测试2.2 提交到集群测试 1、MapReduce概述 1.1 MapReduce定义 MapReduce是一…

WhatsApp获客怎么做?教你五个技巧!

随着全球通信的普及&#xff0c;WhatsApp已经成为人们日常生活中不可或缺的沟通工具&#xff0c;对于跨境电商和外贸企业来说&#xff0c;利用WhatsApp进行获客同样具有巨大的潜力。 那么&#xff0c;如何有效地在WhatsApp上获取客户呢?接下来&#xff0c;我们将为你介绍五个…

Linux系统解决“Key was rejected by service”

Linux系统下加载驱动模块出现如上错误提示的原因为&#xff1a;此驱动未经过签名。 方法一、关闭Secure Boot 如果是物理机&#xff0c;需要开机进入BIOS&#xff0c;找到“Secure Boot”的选项&#xff0c;然后关闭。 如果是虚拟机&#xff0c;可以打开虚拟设置&#xff0c…

物联网与低代码:构建智能化的连接世界

物联网&#xff08;IoT&#xff09;是指通过互联网将各种物理设备、传感器、车辆等连接起来&#xff0c;从而实现数据交互和智能化控制的技术领域。而低代码开发平台则是一种快速构建应用程序的方法&#xff0c;通过简化开发过程&#xff0c;使开发人员能够更迅速地实现创意和创…

Nginx【通俗易懂】《中篇》

目录 1.Url重写rewrite 2.防盗链 3.静态资源压缩 4.跨域问题 1.Url重写rewrite &#x1f929;&#x1f929;&#x1f929; 1.1.rewrite书写格式 rewrite是实现URL重写的关键指令&#xff0c;根据regex&#xff08;正则表达式&#xff09;部分内容&#xff0c;重定向到rep…

MS913/914 25-100MHz 10/12 位用于平面显示器链路Ⅲ的具有直流平衡编码和双向控制通道的串化器和解串器

MS913/MS914 芯片组是 25MHz~100MHz 10 位/12 位 FPD Link III SER/DES(串化器/解串器)&#xff0c;它提供高速 FPD-Link III 接口和高速正向通路以及用于差分对上数据发送的双向 控制通路。广泛应用于车载摄像&#xff0c;医疗设备&#xff0c;管道探测等领 域。 主要特点…