|
@@ -0,0 +1,997 @@
|
|
|
+#include <stdio.h>
|
|
|
+#include <sys/time.h>
|
|
|
+#include <sys/timeb.h>
|
|
|
+#include <unistd.h>
|
|
|
+
|
|
|
+typedef void(*VideoCallback)(unsigned char *buff, int size, double timestamp);
|
|
|
+typedef void(*AudioCallback)(unsigned char *buff, int size, double timestamp);
|
|
|
+typedef void(*RequestCallback)(int offset, int available);
|
|
|
+
|
|
|
+#ifdef __cplusplus
|
|
|
+extern "C" {
|
|
|
+#endif
|
|
|
+
|
|
|
+#include "libavcodec/avcodec.h"
|
|
|
+#include "libavformat/avformat.h"
|
|
|
+#include "libavutil/fifo.h"
|
|
|
+//#include "libswscale/swscale.h"
|
|
|
+
|
|
|
+#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
|
|
|
+
|
|
|
+const int kCustomIoBufferSize = 32 * 1024;
|
|
|
+const int kInitialPcmBufferSize = 128 * 1024;
|
|
|
+const int kDefaultFifoSize = 1 * 1024 * 1024;
|
|
|
+const int kMaxFifoSize = 16 * 1024 * 1024;
|
|
|
+
|
|
|
+typedef enum ErrorCode {
|
|
|
+ kErrorCode_Success = 0,
|
|
|
+ kErrorCode_Invalid_Param,
|
|
|
+ kErrorCode_Invalid_State,
|
|
|
+ kErrorCode_Invalid_Data,
|
|
|
+ kErrorCode_Invalid_Format,
|
|
|
+ kErrorCode_NULL_Pointer,
|
|
|
+ kErrorCode_Open_File_Error,
|
|
|
+ kErrorCode_Eof,
|
|
|
+ kErrorCode_FFmpeg_Error,
|
|
|
+ kErrorCode_Old_Frame
|
|
|
+} ErrorCode;
|
|
|
+
|
|
|
+typedef enum LogLevel {
|
|
|
+ kLogLevel_None, //Not logging.
|
|
|
+ kLogLevel_Core, //Only logging core module(without ffmpeg).
|
|
|
+ kLogLevel_All //Logging all, with ffmpeg.
|
|
|
+} LogLevel;
|
|
|
+
|
|
|
+typedef struct WebDecoder {
|
|
|
+ AVFormatContext *avformatContext;
|
|
|
+ AVCodecContext *videoCodecContext;
|
|
|
+ AVCodecContext *audioCodecContext;
|
|
|
+ AVFrame *avFrame;
|
|
|
+ int videoStreamIdx;
|
|
|
+ int audioStreamIdx;
|
|
|
+ VideoCallback videoCallback;
|
|
|
+ AudioCallback audioCallback;
|
|
|
+ RequestCallback requestCallback;
|
|
|
+ unsigned char *yuvBuffer;
|
|
|
+ //unsigned char *rgbBuffer;
|
|
|
+ unsigned char *pcmBuffer;
|
|
|
+ int currentPcmBufferSize;
|
|
|
+ int videoBufferSize;
|
|
|
+ int videoSize;
|
|
|
+ //struct SwsContext* swsCtx;
|
|
|
+ unsigned char *customIoBuffer;
|
|
|
+ FILE *fp;
|
|
|
+ char fileName[64];
|
|
|
+ int64_t fileSize;
|
|
|
+ int64_t fileReadPos;
|
|
|
+ int64_t fileWritePos;
|
|
|
+ int64_t lastRequestOffset;
|
|
|
+ double beginTimeOffset;
|
|
|
+ int accurateSeek;
|
|
|
+ // For streaming.
|
|
|
+ int isStream;
|
|
|
+ AVFifoBuffer *fifo;
|
|
|
+ int fifoSize;
|
|
|
+} WebDecoder;
|
|
|
+
|
|
|
+WebDecoder *decoder = NULL;
|
|
|
+LogLevel logLevel = kLogLevel_None;
|
|
|
+
|
|
|
+int getAailableDataSize();
|
|
|
+
|
|
|
+unsigned long getTickCount() {
|
|
|
+ struct timespec ts;
|
|
|
+ clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
|
+ return ts.tv_sec * (unsigned long)1000 + ts.tv_nsec / 1000000;
|
|
|
+}
|
|
|
+
|
|
|
+void simpleLog(const char* format, ...) {
|
|
|
+ if (logLevel == kLogLevel_None) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ char szBuffer[1024] = { 0 };
|
|
|
+ char szTime[32] = { 0 };
|
|
|
+ char *p = NULL;
|
|
|
+ int prefixLength = 0;
|
|
|
+ const char *tag = "Core";
|
|
|
+ struct tm tmTime;
|
|
|
+ struct timeb tb;
|
|
|
+
|
|
|
+ ftime(&tb);
|
|
|
+ localtime_r(&tb.time, &tmTime);
|
|
|
+
|
|
|
+ if (1) {
|
|
|
+ int tmYear = tmTime.tm_year + 1900;
|
|
|
+ int tmMon = tmTime.tm_mon + 1;
|
|
|
+ int tmMday = tmTime.tm_mday;
|
|
|
+ int tmHour = tmTime.tm_hour;
|
|
|
+ int tmMin = tmTime.tm_min;
|
|
|
+ int tmSec = tmTime.tm_sec;
|
|
|
+ int tmMillisec = tb.millitm;
|
|
|
+ sprintf(szTime, "%d-%d-%d %d:%d:%d.%d", tmYear, tmMon, tmMday, tmHour, tmMin, tmSec, tmMillisec);
|
|
|
+ }
|
|
|
+
|
|
|
+ prefixLength = sprintf(szBuffer, "[%s][%s][DT] ", szTime, tag);
|
|
|
+ p = szBuffer + prefixLength;
|
|
|
+
|
|
|
+ if (1) {
|
|
|
+ va_list ap;
|
|
|
+ va_start(ap, format);
|
|
|
+ vsnprintf(p, 1024 - prefixLength, format, ap);
|
|
|
+ va_end(ap);
|
|
|
+ }
|
|
|
+
|
|
|
+ printf("%s\n", szBuffer);
|
|
|
+}
|
|
|
+
|
|
|
+void ffmpegLogCallback(void* ptr, int level, const char* fmt, va_list vl) {
|
|
|
+ static int printPrefix = 1;
|
|
|
+ static int count = 0;
|
|
|
+ static char prev[1024] = { 0 };
|
|
|
+ char line[1024] = { 0 };
|
|
|
+ static int is_atty;
|
|
|
+ AVClass* avc = ptr ? *(AVClass**)ptr : NULL;
|
|
|
+ if (level > AV_LOG_DEBUG) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ line[0] = 0;
|
|
|
+
|
|
|
+ if (printPrefix && avc) {
|
|
|
+ if (avc->parent_log_context_offset) {
|
|
|
+ AVClass** parent = *(AVClass***)(((uint8_t*)ptr) + avc->parent_log_context_offset);
|
|
|
+ if (parent && *parent) {
|
|
|
+ snprintf(line, sizeof(line), "[%s @ %p] ", (*parent)->item_name(parent), parent);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ snprintf(line + strlen(line), sizeof(line) - strlen(line), "[%s @ %p] ", avc->item_name(ptr), ptr);
|
|
|
+ }
|
|
|
+
|
|
|
+ vsnprintf(line + strlen(line), sizeof(line) - strlen(line), fmt, vl);
|
|
|
+ line[strlen(line) + 1] = 0;
|
|
|
+ simpleLog("%s", line);
|
|
|
+}
|
|
|
+
|
|
|
+int openCodecContext(AVFormatContext *fmtCtx, enum AVMediaType type, int *streamIdx, AVCodecContext **decCtx) {
|
|
|
+ int ret = 0;
|
|
|
+ do {
|
|
|
+ int streamIndex = -1;
|
|
|
+ AVStream *st = NULL;
|
|
|
+ AVCodec *dec = NULL;
|
|
|
+ AVDictionary *opts = NULL;
|
|
|
+
|
|
|
+ ret = av_find_best_stream(fmtCtx, type, -1, -1, NULL, 0);
|
|
|
+ if (ret < 0) {
|
|
|
+ simpleLog("Could not find %s stream.", av_get_media_type_string(type));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ streamIndex = ret;
|
|
|
+ st = fmtCtx->streams[streamIndex];
|
|
|
+
|
|
|
+ dec = avcodec_find_decoder(st->codecpar->codec_id);
|
|
|
+ if (!dec) {
|
|
|
+ simpleLog("Failed to find %s codec %d.", av_get_media_type_string(type), st->codecpar->codec_id);
|
|
|
+ ret = AVERROR(EINVAL);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ *decCtx = avcodec_alloc_context3(dec);
|
|
|
+ if (!*decCtx) {
|
|
|
+ simpleLog("Failed to allocate the %s codec context.", av_get_media_type_string(type));
|
|
|
+ ret = AVERROR(ENOMEM);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((ret = avcodec_parameters_to_context(*decCtx, st->codecpar)) != 0) {
|
|
|
+ simpleLog("Failed to copy %s codec parameters to decoder context.", av_get_media_type_string(type));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ av_dict_set(&opts, "refcounted_frames", "0", 0);
|
|
|
+
|
|
|
+ if ((ret = avcodec_open2(*decCtx, dec, NULL)) != 0) {
|
|
|
+ simpleLog("Failed to open %s codec.", av_get_media_type_string(type));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ *streamIdx = streamIndex;
|
|
|
+ avcodec_flush_buffers(*decCtx);
|
|
|
+ } while (0);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+void closeCodecContext(AVFormatContext *fmtCtx, AVCodecContext *decCtx, int streamIdx) {
|
|
|
+ do {
|
|
|
+ if (fmtCtx == NULL || decCtx == NULL) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (streamIdx < 0 || streamIdx >= fmtCtx->nb_streams) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ fmtCtx->streams[streamIdx]->discard = AVDISCARD_ALL;
|
|
|
+ avcodec_close(decCtx);
|
|
|
+ } while (0);
|
|
|
+}
|
|
|
+
|
|
|
+ErrorCode copyYuvData(AVFrame *frame, unsigned char *buffer, int width, int height) {
|
|
|
+ ErrorCode ret = kErrorCode_Success;
|
|
|
+ unsigned char *src = NULL;
|
|
|
+ unsigned char *dst = buffer;
|
|
|
+ int i = 0;
|
|
|
+ do {
|
|
|
+ if (frame == NULL || buffer == NULL) {
|
|
|
+ ret = kErrorCode_Invalid_Param;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!frame->data[0] || !frame->data[1] || !frame->data[2]) {
|
|
|
+ ret = kErrorCode_Invalid_Param;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < height; i++) {
|
|
|
+ src = frame->data[0] + i * frame->linesize[0];
|
|
|
+ memcpy(dst, src, width);
|
|
|
+ dst += width;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < height / 2; i++) {
|
|
|
+ src = frame->data[1] + i * frame->linesize[1];
|
|
|
+ memcpy(dst, src, width / 2);
|
|
|
+ dst += width / 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < height / 2; i++) {
|
|
|
+ src = frame->data[2] + i * frame->linesize[2];
|
|
|
+ memcpy(dst, src, width / 2);
|
|
|
+ dst += width / 2;
|
|
|
+ }
|
|
|
+ } while (0);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ErrorCode yuv420pToRgb32(unsigned char *yuvBuff, unsigned char *rgbBuff, int width, int height) {
|
|
|
+ ErrorCode ret = kErrorCode_Success;
|
|
|
+ AVPicture yuvPicture, rgbPicture;
|
|
|
+ uint8_t *ptmp = NULL;
|
|
|
+ do {
|
|
|
+ if (yuvBuff == NULL || rgbBuff == NULL) {
|
|
|
+ ret = kErrorCode_Invalid_Param
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (decoder == NULL || decoder->swsCtx == NULL) {
|
|
|
+ ret = kErrorCode_Invalid_Param
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ avpicture_fill(&yuvPicture, yuvBuff, AV_PIX_FMT_YUV420P, width, height);
|
|
|
+ avpicture_fill(&rgbPicture, rgbBuff, AV_PIX_FMT_RGB32, width, height);
|
|
|
+
|
|
|
+ ptmp = yuvPicture.data[1];
|
|
|
+ yuvPicture.data[1] = yuvPicture.data[2];
|
|
|
+ yuvPicture.data[2] = ptmp;
|
|
|
+
|
|
|
+ sws_scale(decoder->swsCtx, yuvPicture.data, yuvPicture.linesize, 0, height, rgbPicture.data, rgbPicture.linesize);
|
|
|
+ } while (0);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+*/
|
|
|
+
|
|
|
+int roundUp(int numToRound, int multiple) {
|
|
|
+ return (numToRound + multiple - 1) & -multiple;
|
|
|
+}
|
|
|
+
|
|
|
+ErrorCode processDecodedVideoFrame(AVFrame *frame) {
|
|
|
+ ErrorCode ret = kErrorCode_Success;
|
|
|
+ double timestamp = 0.0f;
|
|
|
+ do {
|
|
|
+ if (frame == NULL ||
|
|
|
+ decoder->videoCallback == NULL ||
|
|
|
+ decoder->yuvBuffer == NULL ||
|
|
|
+ decoder->videoBufferSize <= 0) {
|
|
|
+ ret = kErrorCode_Invalid_Param;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (decoder->videoCodecContext->pix_fmt != AV_PIX_FMT_YUV420P) {
|
|
|
+ simpleLog("Not YUV420P, but unsupported format %d.", decoder->videoCodecContext->pix_fmt);
|
|
|
+ ret = kErrorCode_Invalid_Format;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = copyYuvData(frame, decoder->yuvBuffer, decoder->videoCodecContext->width, decoder->videoCodecContext->height);
|
|
|
+ if (ret != kErrorCode_Success) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ ret = yuv420pToRgb32(decoder->yuvBuffer, decoder->rgbBuffer, decoder->videoCodecContext->width, decoder->videoCodecContext->height);
|
|
|
+ if (ret != kErrorCode_Success) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ */
|
|
|
+
|
|
|
+ timestamp = (double)frame->pts * av_q2d(decoder->avformatContext->streams[decoder->videoStreamIdx]->time_base);
|
|
|
+
|
|
|
+ if (decoder->accurateSeek && timestamp < decoder->beginTimeOffset) {
|
|
|
+ //simpleLog("video timestamp %lf < %lf", timestamp, decoder->beginTimeOffset);
|
|
|
+ ret = kErrorCode_Old_Frame;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ decoder->videoCallback(decoder->yuvBuffer, decoder->videoSize, timestamp);
|
|
|
+ } while (0);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+ErrorCode processDecodedAudioFrame(AVFrame *frame) {
|
|
|
+ ErrorCode ret = kErrorCode_Success;
|
|
|
+ int sampleSize = 0;
|
|
|
+ int audioDataSize = 0;
|
|
|
+ int targetSize = 0;
|
|
|
+ int offset = 0;
|
|
|
+ int i = 0;
|
|
|
+ int ch = 0;
|
|
|
+ double timestamp = 0.0f;
|
|
|
+ do {
|
|
|
+ if (frame == NULL) {
|
|
|
+ ret = kErrorCode_Invalid_Param;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ sampleSize = av_get_bytes_per_sample(decoder->audioCodecContext->sample_fmt);
|
|
|
+ if (sampleSize < 0) {
|
|
|
+ simpleLog("Failed to calculate data size.");
|
|
|
+ ret = kErrorCode_Invalid_Data;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (decoder->pcmBuffer == NULL) {
|
|
|
+ decoder->pcmBuffer = (unsigned char*)av_mallocz(kInitialPcmBufferSize);
|
|
|
+ decoder->currentPcmBufferSize = kInitialPcmBufferSize;
|
|
|
+ simpleLog("Initial PCM buffer size %d.", decoder->currentPcmBufferSize);
|
|
|
+ }
|
|
|
+
|
|
|
+ audioDataSize = frame->nb_samples * decoder->audioCodecContext->channels * sampleSize;
|
|
|
+ if (decoder->currentPcmBufferSize < audioDataSize) {
|
|
|
+ targetSize = roundUp(audioDataSize, 4);
|
|
|
+ simpleLog("Current PCM buffer size %d not sufficient for data size %d, round up to target %d.",
|
|
|
+ decoder->currentPcmBufferSize,
|
|
|
+ audioDataSize,
|
|
|
+ targetSize);
|
|
|
+ decoder->currentPcmBufferSize = targetSize;
|
|
|
+ av_free(decoder->pcmBuffer);
|
|
|
+ decoder->pcmBuffer = (unsigned char*)av_mallocz(decoder->currentPcmBufferSize);
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i = 0; i < frame->nb_samples; i++) {
|
|
|
+ for (ch = 0; ch < decoder->audioCodecContext->channels; ch++) {
|
|
|
+ memcpy(decoder->pcmBuffer + offset, frame->data[ch] + sampleSize * i, sampleSize);
|
|
|
+ offset += sampleSize;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ timestamp = (double)frame->pts * av_q2d(decoder->avformatContext->streams[decoder->audioStreamIdx]->time_base);
|
|
|
+
|
|
|
+ if (decoder->accurateSeek && timestamp < decoder->beginTimeOffset) {
|
|
|
+ //simpleLog("audio timestamp %lf < %lf", timestamp, decoder->beginTimeOffset);
|
|
|
+ ret = kErrorCode_Old_Frame;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (decoder->audioCallback != NULL) {
|
|
|
+ decoder->audioCallback(decoder->pcmBuffer, audioDataSize, timestamp);
|
|
|
+ }
|
|
|
+ } while (0);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+ErrorCode decodePacket(AVPacket *pkt, int *decodedLen) {
|
|
|
+ int ret = 0;
|
|
|
+ int isVideo = 0;
|
|
|
+ AVCodecContext *codecContext = NULL;
|
|
|
+
|
|
|
+ if (pkt == NULL || decodedLen == NULL) {
|
|
|
+ simpleLog("decodePacket invalid param.");
|
|
|
+ return kErrorCode_Invalid_Param;
|
|
|
+ }
|
|
|
+
|
|
|
+ *decodedLen = 0;
|
|
|
+
|
|
|
+ if (pkt->stream_index == decoder->videoStreamIdx) {
|
|
|
+ codecContext = decoder->videoCodecContext;
|
|
|
+ isVideo = 1;
|
|
|
+ } else if (pkt->stream_index == decoder->audioStreamIdx) {
|
|
|
+ codecContext = decoder->audioCodecContext;
|
|
|
+ isVideo = 0;
|
|
|
+ } else {
|
|
|
+ return kErrorCode_Invalid_Data;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = avcodec_send_packet(codecContext, pkt);
|
|
|
+ if (ret < 0) {
|
|
|
+ simpleLog("Error sending a packet for decoding %d.", ret);
|
|
|
+ return kErrorCode_FFmpeg_Error;
|
|
|
+ }
|
|
|
+
|
|
|
+ while (ret >= 0) {
|
|
|
+ ret = avcodec_receive_frame(codecContext, decoder->avFrame);
|
|
|
+ if (ret == AVERROR(EAGAIN)) {
|
|
|
+ return kErrorCode_Success;
|
|
|
+ } else if (ret == AVERROR_EOF) {
|
|
|
+ return kErrorCode_Eof;
|
|
|
+ } else if (ret < 0) {
|
|
|
+ simpleLog("Error during decoding %d.", ret);
|
|
|
+ return kErrorCode_FFmpeg_Error;
|
|
|
+ } else {
|
|
|
+ int r = isVideo ? processDecodedVideoFrame(decoder->avFrame) : processDecodedAudioFrame(decoder->avFrame);
|
|
|
+ if (r == kErrorCode_Old_Frame) {
|
|
|
+ return r;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ *decodedLen = pkt->size;
|
|
|
+ return kErrorCode_Success;
|
|
|
+}
|
|
|
+
|
|
|
+int readFromFile(uint8_t *data, int len) {
|
|
|
+ //simpleLog("readFromFile %d.", len);
|
|
|
+ int32_t ret = -1;
|
|
|
+ int availableBytes = 0;
|
|
|
+ int canReadLen = 0;
|
|
|
+ do {
|
|
|
+ if (decoder->fp == NULL) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ availableBytes = decoder->fileWritePos - decoder->fileReadPos;
|
|
|
+ if (availableBytes <= 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ fseek(decoder->fp, decoder->fileReadPos, SEEK_SET);
|
|
|
+ canReadLen = MIN(availableBytes, len);
|
|
|
+ fread(data, canReadLen, 1, decoder->fp);
|
|
|
+ decoder->fileReadPos += canReadLen;
|
|
|
+ ret = canReadLen;
|
|
|
+ } while (0);
|
|
|
+ //simpleLog("readFromFile ret %d.", ret);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+int readFromFifo(uint8_t *data, int len) {
|
|
|
+ //simpleLog("readFromFifo %d.", len);
|
|
|
+ int32_t ret = -1;
|
|
|
+ int availableBytes = 0;
|
|
|
+ int canReadLen = 0;
|
|
|
+ do {
|
|
|
+ if (decoder->fifo == NULL) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ availableBytes = av_fifo_size(decoder->fifo);
|
|
|
+ if (availableBytes <= 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ canReadLen = MIN(availableBytes, len);
|
|
|
+ av_fifo_generic_read(decoder->fifo, data, canReadLen, NULL);
|
|
|
+ ret = canReadLen;
|
|
|
+ } while (0);
|
|
|
+ //simpleLog("readFromFifo ret %d, left %d.", ret, av_fifo_size(decoder->fifo));
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+int readCallback(void *opaque, uint8_t *data, int len) {
|
|
|
+ //simpleLog("readCallback %d.", len);
|
|
|
+ int32_t ret = -1;
|
|
|
+ do {
|
|
|
+ if (decoder == NULL) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (data == NULL || len <= 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = decoder->isStream ? readFromFifo(data, len) : readFromFile(data, len);
|
|
|
+ } while (0);
|
|
|
+ //simpleLog("readCallback ret %d.", ret);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+int64_t seekCallback(void *opaque, int64_t offset, int whence) {
|
|
|
+ int64_t ret = -1;
|
|
|
+ int64_t pos = -1;
|
|
|
+ int64_t req_pos = -1;
|
|
|
+ //simpleLog("seekCallback %lld %d.", offset, whence);
|
|
|
+ do {
|
|
|
+ if (decoder == NULL || decoder->isStream || decoder->fp == NULL) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (whence == AVSEEK_SIZE) {
|
|
|
+ ret = decoder->fileSize;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (whence != SEEK_END && whence != SEEK_SET && whence != SEEK_CUR) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = fseek(decoder->fp, (long)offset, whence);
|
|
|
+ if (ret == -1) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ pos = (int64_t)ftell(decoder->fp);
|
|
|
+ if (pos < decoder->lastRequestOffset || pos > decoder->fileWritePos) {
|
|
|
+ decoder->lastRequestOffset = pos;
|
|
|
+ decoder->fileReadPos = pos;
|
|
|
+ decoder->fileWritePos = pos;
|
|
|
+ req_pos = pos;
|
|
|
+ ret = -1; // Forcing not to call read at once.
|
|
|
+ decoder->requestCallback(pos, getAailableDataSize());
|
|
|
+ simpleLog("Will request %lld and return %lld.", pos, ret);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ decoder->fileReadPos = pos;
|
|
|
+ ret = pos;
|
|
|
+ } while (0);
|
|
|
+ //simpleLog("seekCallback return %lld.", ret);
|
|
|
+
|
|
|
+ if (decoder != NULL && decoder->requestCallback != NULL) {
|
|
|
+ decoder->requestCallback(req_pos, getAailableDataSize());
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+int writeToFile(unsigned char *buff, int size) {
|
|
|
+ int ret = 0;
|
|
|
+ int64_t leftBytes = 0;
|
|
|
+ int canWriteBytes = 0;
|
|
|
+ do {
|
|
|
+ if (decoder->fp == NULL) {
|
|
|
+ ret = -1;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ leftBytes = decoder->fileSize - decoder->fileWritePos;
|
|
|
+ if (leftBytes <= 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ canWriteBytes = MIN(leftBytes, size);
|
|
|
+ fseek(decoder->fp, decoder->fileWritePos, SEEK_SET);
|
|
|
+ fwrite(buff, canWriteBytes, 1, decoder->fp);
|
|
|
+ decoder->fileWritePos += canWriteBytes;
|
|
|
+ ret = canWriteBytes;
|
|
|
+ } while (0);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+int writeToFifo(unsigned char *buff, int size) {
|
|
|
+ int ret = 0;
|
|
|
+ do {
|
|
|
+ if (decoder->fifo == NULL) {
|
|
|
+ ret = -1;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ int64_t leftSpace = av_fifo_space(decoder->fifo);
|
|
|
+ if (leftSpace < size) {
|
|
|
+ int growSize = 0;
|
|
|
+ do {
|
|
|
+ leftSpace += decoder->fifoSize;
|
|
|
+ growSize += decoder->fifoSize;
|
|
|
+ decoder->fifoSize += decoder->fifoSize;
|
|
|
+ } while (leftSpace < size);
|
|
|
+ av_fifo_grow(decoder->fifo, growSize);
|
|
|
+
|
|
|
+ simpleLog("Fifo size growed to %d.", decoder->fifoSize);
|
|
|
+ if (decoder->fifoSize >= kMaxFifoSize) {
|
|
|
+ simpleLog("[Warn] Fifo size larger than %d.", kMaxFifoSize);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //simpleLog("Wrote %d bytes to fifo, total %d.", size, av_fifo_size(decoder->fifo));
|
|
|
+ ret = av_fifo_generic_write(decoder->fifo, buff, size, NULL);
|
|
|
+ } while (0);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+int getAailableDataSize() {
|
|
|
+ int ret = 0;
|
|
|
+ do {
|
|
|
+ if (decoder == NULL) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (decoder->isStream) {
|
|
|
+ ret = decoder->fifo == NULL ? 0 : av_fifo_size(decoder->fifo);
|
|
|
+ } else {
|
|
|
+ ret = decoder->fileWritePos - decoder->fileReadPos;
|
|
|
+ }
|
|
|
+ } while (0);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+//////////////////////////////////Export methods////////////////////////////////////////
|
|
|
+ErrorCode initDecoder(int fileSize, int logLv) {
|
|
|
+ ErrorCode ret = kErrorCode_Success;
|
|
|
+ do {
|
|
|
+ //Log level.
|
|
|
+ logLevel = logLv;
|
|
|
+
|
|
|
+ if (decoder != NULL) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ decoder = (WebDecoder *)av_mallocz(sizeof(WebDecoder));
|
|
|
+ if (fileSize >= 0) {
|
|
|
+ decoder->fileSize = fileSize;
|
|
|
+ sprintf(decoder->fileName, "tmp-%lu.mp4", getTickCount());
|
|
|
+ decoder->fp = fopen(decoder->fileName, "wb+");
|
|
|
+ if (decoder->fp == NULL) {
|
|
|
+ simpleLog("Open file %s failed, err: %d.", decoder->fileName, errno);
|
|
|
+ ret = kErrorCode_Open_File_Error;
|
|
|
+ av_free(decoder);
|
|
|
+ decoder = NULL;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ decoder->isStream = 1;
|
|
|
+ decoder->fifoSize = kDefaultFifoSize;
|
|
|
+ decoder->fifo = av_fifo_alloc(decoder->fifoSize);
|
|
|
+ }
|
|
|
+ } while (0);
|
|
|
+ simpleLog("Decoder initialized %d.", ret);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+ErrorCode uninitDecoder() {
|
|
|
+ if (decoder != NULL) {
|
|
|
+ if (decoder->fp != NULL) {
|
|
|
+ fclose(decoder->fp);
|
|
|
+ decoder->fp = NULL;
|
|
|
+ remove(decoder->fileName);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (decoder->fifo != NULL) {
|
|
|
+ av_fifo_freep(&decoder->fifo);
|
|
|
+ }
|
|
|
+
|
|
|
+ av_freep(&decoder);
|
|
|
+ }
|
|
|
+
|
|
|
+ av_log_set_callback(NULL);
|
|
|
+
|
|
|
+ simpleLog("Decoder uninitialized.");
|
|
|
+ return kErrorCode_Success;
|
|
|
+}
|
|
|
+
|
|
|
+ErrorCode openDecoder(int *paramArray, int paramCount, long videoCallback, long audioCallback, long requestCallback) {
|
|
|
+ ErrorCode ret = kErrorCode_Success;
|
|
|
+ int r = 0;
|
|
|
+ int i = 0;
|
|
|
+ int params[7] = { 0 };
|
|
|
+ do {
|
|
|
+ simpleLog("打开编码器.");
|
|
|
+
|
|
|
+ av_register_all();
|
|
|
+ avcodec_register_all();
|
|
|
+
|
|
|
+ if (logLevel == kLogLevel_All) {
|
|
|
+ av_log_set_callback(ffmpegLogCallback);
|
|
|
+ }
|
|
|
+
|
|
|
+ decoder->avformatContext = avformat_alloc_context();
|
|
|
+ decoder->customIoBuffer = (unsigned char*)av_mallocz(kCustomIoBufferSize);
|
|
|
+
|
|
|
+ AVIOContext* ioContext = avio_alloc_context(
|
|
|
+ decoder->customIoBuffer,
|
|
|
+ kCustomIoBufferSize,
|
|
|
+ 0,
|
|
|
+ NULL,
|
|
|
+ readCallback,
|
|
|
+ NULL,
|
|
|
+ seekCallback);
|
|
|
+ if (ioContext == NULL) {
|
|
|
+ ret = kErrorCode_FFmpeg_Error;
|
|
|
+ simpleLog("avio_alloc_context failed.");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ decoder->avformatContext->pb = ioContext;
|
|
|
+ decoder->avformatContext->flags = AVFMT_FLAG_CUSTOM_IO;
|
|
|
+ simpleLog("avformat_open_input.");
|
|
|
+
|
|
|
+ r = avformat_open_input(&decoder->avformatContext, NULL, NULL, NULL);
|
|
|
+ if (r != 0) {
|
|
|
+ ret = kErrorCode_FFmpeg_Error;
|
|
|
+ char err_info[32] = { 0 };
|
|
|
+ av_strerror(ret, err_info, 32);
|
|
|
+ simpleLog("avformat_open_input failed %d %s.", ret, err_info);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ simpleLog("avformat_find_stream_info");
|
|
|
+
|
|
|
+ r = avformat_find_stream_info(decoder->avformatContext, NULL);
|
|
|
+ if (r != 0) {
|
|
|
+ ret = kErrorCode_FFmpeg_Error;
|
|
|
+ simpleLog("av_find_stream_info failed %d.", ret);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ simpleLog("avformat_find_stream_info 成功.");
|
|
|
+
|
|
|
+ for (i = 0; i < decoder->avformatContext->nb_streams; i++) {
|
|
|
+ decoder->avformatContext->streams[i]->discard = AVDISCARD_DEFAULT;
|
|
|
+ }
|
|
|
+
|
|
|
+ r = openCodecContext(
|
|
|
+ decoder->avformatContext,
|
|
|
+ AVMEDIA_TYPE_VIDEO,
|
|
|
+ &decoder->videoStreamIdx,
|
|
|
+ &decoder->videoCodecContext);
|
|
|
+ if (r != 0) {
|
|
|
+ ret = kErrorCode_FFmpeg_Error;
|
|
|
+ simpleLog("Open video codec context failed %d.", ret);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ simpleLog("Open video codec context success, video stream index %d %x.",
|
|
|
+ decoder->videoStreamIdx, (unsigned int)decoder->videoCodecContext);
|
|
|
+
|
|
|
+ simpleLog("Video stream index:%d pix_fmt:%d resolution:%d*%d.",
|
|
|
+ decoder->videoStreamIdx,
|
|
|
+ decoder->videoCodecContext->pix_fmt,
|
|
|
+ decoder->videoCodecContext->width,
|
|
|
+ decoder->videoCodecContext->height);
|
|
|
+
|
|
|
+ r = openCodecContext(
|
|
|
+ decoder->avformatContext,
|
|
|
+ AVMEDIA_TYPE_AUDIO,
|
|
|
+ &decoder->audioStreamIdx,
|
|
|
+ &decoder->audioCodecContext);
|
|
|
+ if (r != 0) {
|
|
|
+ ret = kErrorCode_FFmpeg_Error;
|
|
|
+ simpleLog("Open audio codec context failed %d.", ret);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ simpleLog("Open audio codec context success, audio stream index %d %x.",
|
|
|
+ decoder->audioStreamIdx, (unsigned int)decoder->audioCodecContext);
|
|
|
+
|
|
|
+ simpleLog("Audio stream index:%d sample_fmt:%d channel:%d, sample rate:%d.",
|
|
|
+ decoder->audioStreamIdx,
|
|
|
+ decoder->audioCodecContext->sample_fmt,
|
|
|
+ decoder->audioCodecContext->channels,
|
|
|
+ decoder->audioCodecContext->sample_rate);
|
|
|
+
|
|
|
+ av_seek_frame(decoder->avformatContext, -1, 0, AVSEEK_FLAG_BACKWARD);
|
|
|
+
|
|
|
+ /* For RGB Renderer(2D WebGL).
|
|
|
+ decoder->swsCtx = sws_getContext(
|
|
|
+ decoder->videoCodecContext->width,
|
|
|
+ decoder->videoCodecContext->height,
|
|
|
+ decoder->videoCodecContext->pix_fmt,
|
|
|
+ decoder->videoCodecContext->width,
|
|
|
+ decoder->videoCodecContext->height,
|
|
|
+ AV_PIX_FMT_RGB32,
|
|
|
+ SWS_BILINEAR,
|
|
|
+ 0,
|
|
|
+ 0,
|
|
|
+ 0);
|
|
|
+ if (decoder->swsCtx == NULL) {
|
|
|
+ simpleLog("sws_getContext failed.");
|
|
|
+ ret = kErrorCode_FFmpeg_Error;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ */
|
|
|
+
|
|
|
+ decoder->videoSize = avpicture_get_size(
|
|
|
+ decoder->videoCodecContext->pix_fmt,
|
|
|
+ decoder->videoCodecContext->width,
|
|
|
+ decoder->videoCodecContext->height);
|
|
|
+
|
|
|
+ decoder->videoBufferSize = 3 * decoder->videoSize;
|
|
|
+ decoder->yuvBuffer = (unsigned char *)av_mallocz(decoder->videoBufferSize);
|
|
|
+ decoder->avFrame = av_frame_alloc();
|
|
|
+
|
|
|
+ params[0] = 1000 * (decoder->avformatContext->duration + 5000) / AV_TIME_BASE;
|
|
|
+ params[1] = decoder->videoCodecContext->pix_fmt;
|
|
|
+ params[2] = decoder->videoCodecContext->width;
|
|
|
+ params[3] = decoder->videoCodecContext->height;
|
|
|
+ params[4] = decoder->audioCodecContext->sample_fmt;
|
|
|
+ params[5] = decoder->audioCodecContext->channels;
|
|
|
+ params[6] = decoder->audioCodecContext->sample_rate;
|
|
|
+
|
|
|
+ enum AVSampleFormat sampleFmt = decoder->audioCodecContext->sample_fmt;
|
|
|
+ if (av_sample_fmt_is_planar(sampleFmt)) {
|
|
|
+ const char *packed = av_get_sample_fmt_name(sampleFmt);
|
|
|
+ params[4] = av_get_packed_sample_fmt(sampleFmt);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (paramArray != NULL && paramCount > 0) {
|
|
|
+ for (int i = 0; i < paramCount; ++i) {
|
|
|
+ paramArray[i] = params[i];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ decoder->videoCallback = (VideoCallback)videoCallback;
|
|
|
+ decoder->audioCallback = (AudioCallback)audioCallback;
|
|
|
+ decoder->requestCallback = (RequestCallback)requestCallback;
|
|
|
+
|
|
|
+ simpleLog("Decoder opened, duration %ds, picture size %d.", params[0], decoder->videoSize);
|
|
|
+ } while (0);
|
|
|
+
|
|
|
+ if (ret != kErrorCode_Success && decoder != NULL) {
|
|
|
+ av_freep(&decoder);
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+ErrorCode closeDecoder() {
|
|
|
+ ErrorCode ret = kErrorCode_Success;
|
|
|
+ do {
|
|
|
+ if (decoder == NULL || decoder->avformatContext == NULL) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (decoder->videoCodecContext != NULL) {
|
|
|
+ closeCodecContext(decoder->avformatContext, decoder->videoCodecContext, decoder->videoStreamIdx);
|
|
|
+ decoder->videoCodecContext = NULL;
|
|
|
+ simpleLog("Video codec context closed.");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (decoder->audioCodecContext != NULL) {
|
|
|
+ closeCodecContext(decoder->avformatContext, decoder->audioCodecContext, decoder->audioStreamIdx);
|
|
|
+ decoder->audioCodecContext = NULL;
|
|
|
+ simpleLog("Audio codec context closed.");
|
|
|
+ }
|
|
|
+
|
|
|
+ AVIOContext *pb = decoder->avformatContext->pb;
|
|
|
+ if (pb != NULL) {
|
|
|
+ if (pb->buffer != NULL) {
|
|
|
+ av_freep(&pb->buffer);
|
|
|
+ decoder->customIoBuffer = NULL;
|
|
|
+ }
|
|
|
+ av_freep(&decoder->avformatContext->pb);
|
|
|
+ simpleLog("IO context released.");
|
|
|
+ }
|
|
|
+
|
|
|
+ avformat_close_input(&decoder->avformatContext);
|
|
|
+ decoder->avformatContext = NULL;
|
|
|
+ simpleLog("Input closed.");
|
|
|
+
|
|
|
+ if (decoder->yuvBuffer != NULL) {
|
|
|
+ av_freep(&decoder->yuvBuffer);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (decoder->pcmBuffer != NULL) {
|
|
|
+ av_freep(&decoder->pcmBuffer);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (decoder->avFrame != NULL) {
|
|
|
+ av_freep(&decoder->avFrame);
|
|
|
+ }
|
|
|
+ simpleLog("All buffer released.");
|
|
|
+ } while (0);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+int sendData(unsigned char *buff, int size) {
|
|
|
+ int ret = 0;
|
|
|
+ int64_t leftBytes = 0;
|
|
|
+ int canWriteBytes = 0;
|
|
|
+ do {
|
|
|
+ if (decoder == NULL) {
|
|
|
+ ret = -1;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (buff == NULL || size == 0) {
|
|
|
+ ret = -2;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = decoder->isStream ? writeToFifo(buff, size) : writeToFile(buff, size);
|
|
|
+ } while (0);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+ErrorCode decodeOnePacket() {
|
|
|
+ ErrorCode ret = kErrorCode_Success;
|
|
|
+ int decodedLen = 0;
|
|
|
+ int r = 0;
|
|
|
+
|
|
|
+ AVPacket packet;
|
|
|
+ av_init_packet(&packet);
|
|
|
+ do {
|
|
|
+ if (decoder == NULL) {
|
|
|
+ ret = kErrorCode_Invalid_State;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (getAailableDataSize() <= 0) {
|
|
|
+ ret = kErrorCode_Invalid_State;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ packet.data = NULL;
|
|
|
+ packet.size = 0;
|
|
|
+
|
|
|
+ r = av_read_frame(decoder->avformatContext, &packet);
|
|
|
+ if (r == AVERROR_EOF) {
|
|
|
+ ret = kErrorCode_Eof;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (r < 0 || packet.size == 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ do {
|
|
|
+ ret = decodePacket(&packet, &decodedLen);
|
|
|
+ if (ret != kErrorCode_Success) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (decodedLen <= 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ packet.data += decodedLen;
|
|
|
+ packet.size -= decodedLen;
|
|
|
+ } while (packet.size > 0);
|
|
|
+ } while (0);
|
|
|
+ av_packet_unref(&packet);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+ErrorCode seekTo(int ms, int accurateSeek) {
|
|
|
+ int ret = 0;
|
|
|
+ int64_t pts = (int64_t)ms * 1000;
|
|
|
+ decoder->accurateSeek = accurateSeek;
|
|
|
+ ret = avformat_seek_file(decoder->avformatContext,
|
|
|
+ -1,
|
|
|
+ INT64_MIN,
|
|
|
+ pts,
|
|
|
+ pts,
|
|
|
+ AVSEEK_FLAG_BACKWARD);
|
|
|
+ simpleLog("Native seek to %d return %d %d.", ms, ret, decoder->accurateSeek);
|
|
|
+ if (ret == -1) {
|
|
|
+ return kErrorCode_FFmpeg_Error;
|
|
|
+ } else {
|
|
|
+ avcodec_flush_buffers(decoder->videoCodecContext);
|
|
|
+ avcodec_flush_buffers(decoder->audioCodecContext);
|
|
|
+
|
|
|
+ // Trigger seek callback
|
|
|
+ AVPacket packet;
|
|
|
+ av_init_packet(&packet);
|
|
|
+ av_read_frame(decoder->avformatContext, &packet);
|
|
|
+
|
|
|
+ decoder->beginTimeOffset = (double)ms / 1000;
|
|
|
+ return kErrorCode_Success;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+int main() {
|
|
|
+ //simpleLog("Native loaded.");
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+#ifdef __cplusplus
|
|
|
+}
|
|
|
+#endif
|