AVPKit
VideoPicture.cpp
1 /*******************************************************************************
2  * Copyright (c) 2024, 2026, Olivier Ayache. All rights reserved.
3  *
4  * This file is part of AVPKit.
5  *
6  * AVPKit is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * AVPKit is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with AVPKit. If not, see <http://www.gnu.org/licenses/>.
18  *******************************************************************************/
19 
20 #include <stdexcept>
21 // for memset
22 #include <cstring>
23 #include <com/avpkit/ferry/Logger.h>
24 #include <com/avpkit/ferry/RefPointer.h>
25 #include <com/avpkit/core/Global.h>
26 #include "com/avpkit/core/VideoPicture.h"
27 
28 extern "C" {
29 #include <libavcodec/mediacodec.h>
30 #ifdef __APPLE__
31 #include <CoreMedia/CMSampleBuffer.h>
32 #endif
33 }
34 
35 VS_LOG_SETUP(VS_CPP_PACKAGE);
36 
37 namespace com { namespace avpkit { namespace core
38 {
39 
40  VideoPicture :: VideoPicture()
41  {
42  mIsComplete = false;
43  mFrame = av_frame_alloc();
44  if (!mFrame)
45  throw std::bad_alloc();
46  // Set the private data pointer to point to me.
47  mFrame->opaque = this;
48  mFrame->width = -1;
49  mFrame->height = -1;
50 
51  mFrame->format = (int) IPixelFormat::NONE;
52  mTimeBase = IRational::make(1, 1000000);
53 
54 #ifdef __APPLE__
55  mCMSampleBuffer = NULL;
56 #endif
57 
58  }
59 
60  VideoPicture :: ~VideoPicture()
61  {
62  if (mFrame)
63  av_free(mFrame);
64  mFrame = 0;
65  mBuffer = 0;
66  mTimeBase = 0;
67 #ifdef __APPLE__
68  if (mCMSampleBuffer){
69  CFRelease(mCMSampleBuffer);
70  }
71 #endif
72  }
73 
74  VideoPicture*
75  VideoPicture :: make(IPixelFormat::Type format, int width, int height)
76  {
77  VideoPicture * retval=0;
78  try {
79  retval = VideoPicture::make();
80  retval->mFrame->format = format;
81  retval->mFrame->width = width;
82  retval->mFrame->height = height;
83  // default new frames to be key frames
84  retval->setKeyFrame(true);
85  }
86  catch (std::bad_alloc &e)
87  {
88  VS_REF_RELEASE(retval);
89  throw e;
90  }
91  catch (std::exception& e)
92  {
93  VS_LOG_DEBUG("error: %s", e.what());
94  VS_REF_RELEASE(retval);
95  }
96 
97  return retval;
98  }
99 
100  VideoPicture*
103  int width, int height)
104  {
105  if (!buffer)
106  return 0;
107  VideoPicture *retval = 0;
108  try {
109  retval = make(format, width,height);
110  if (!retval)
111  throw std::bad_alloc();
112 
113  int32_t size = retval->getSize();
114  if (size > 0 && size > buffer->getBufferSize())
115  throw std::runtime_error("input buffer is not large enough for given picture");
116 
118  retval->mBuffer.reset(buffer, true);
120  unsigned char* bytes = (unsigned char*)buffer->getBytes(0, size);
121  if (!bytes)
122  throw std::runtime_error("could not access raw memory in buffer");
123 
124  (void) av_image_fill_arrays(retval->mFrame->data,
125  retval->mFrame->linesize,
126  bytes,
127  (AVPixelFormat) format,
128  width,
129  height,
130  1);
131 
132  }
133  catch (std::bad_alloc &e)
134  {
135  VS_REF_RELEASE(retval);
136  throw e;
137  }
138  catch (std::exception& e)
139  {
140  VS_LOG_DEBUG("error: %s", e.what());
141  VS_REF_RELEASE(retval);
142  }
143  return retval;
144  }
145 
146  void
148  {
149  if (!buffer) return;
151  mBuffer.reset(buffer, true);
152  }
153 
154  bool
156  {
157  bool result = false;
158  try
159  {
160  if (!srcFrame)
161  throw std::runtime_error("empty source frame to copy");
162 
163  if (!srcFrame->isComplete())
164  throw std::runtime_error("source frame is not complete");
165 
166  VideoPicture* src = static_cast<VideoPicture*>(srcFrame);
167  if (!src)
168  throw std::runtime_error("src frame is not of right subtype");
169 
170  // now copy the data
171  allocInternalFrameBuffer();
172 
173  // get the raw buffers
174  unsigned char* srcBuffer = (unsigned char*)src->mBuffer->getBytes(0, src->getSize());
175  unsigned char* dstBuffer = (unsigned char*)mBuffer->getBytes(0, getSize());
176  if (!srcBuffer || !dstBuffer)
177  throw std::runtime_error("could not get buffer to copy");
178  memcpy(dstBuffer, srcBuffer, getSize());
179 
180  this->setComplete(true,
181  srcFrame->getPixelType(),
182  srcFrame->getWidth(),
183  srcFrame->getHeight(),
184  srcFrame->getPts());
185  result = true;
186  }
187  catch (std::exception & e)
188  {
189  VS_LOG_DEBUG("error: %s", e.what());
190  result = false;
191  }
192  return result;
193  }
194 
197  {
198  com::avpkit::ferry::IBuffer *retval = 0;
199  try {
200  if (getSize() > 0) {
201  if (!mBuffer || mBuffer->getBufferSize() < getSize())
202  {
203  allocInternalFrameBuffer();
204  }
205  retval = mBuffer.get();
206  if (!retval) {
207  throw std::bad_alloc();
208  }
209  }
210  } catch (std::bad_alloc &e) {
211  VS_REF_RELEASE(retval);
212  throw e;
213  } catch (std::exception & e)
214  {
215  VS_LOG_DEBUG("Error: %s", e.what());
216  VS_REF_RELEASE(retval);
217  }
218  return retval;
219  }
220 
221  void
222  VideoPicture::render(bool drop, int64_t timeStamp) {
223  if (mFrame && (av_pix_fmt_desc_get((AVPixelFormat) mFrame->format)->flags & AV_PIX_FMT_FLAG_HWACCEL)) {
224  #ifdef __ANDROID__
225  mediacodec_render_frame(mFrame, timeStamp, drop);
226  #elif __APPLE__
227  CVImageBufferRef image = (CVImageBufferRef) mFrame->data[3];
228  CMVideoFormatDescriptionRef formatDescription;
229  if (mCMSampleBuffer){
230  CFRelease(mCMSampleBuffer);
231  }
232  CMTime duration = kCMTimeIndefinite;
233  CMTime pts = CMTimeMake(timeStamp , 1000000000);
234  const CMSampleTimingInfo timingInfo = {
235  .decodeTimeStamp = kCMTimeInvalid,
236  .duration = duration,
237  .presentationTimeStamp = pts
238  };
239  CMVideoFormatDescriptionCreateForImageBuffer(NULL, image, &formatDescription);
240  CMSampleBufferCreateReadyWithImageBuffer(NULL, image, formatDescription, &timingInfo, &mCMSampleBuffer);
241  return sampleBuffer;
242  #endif
243  }
244  }
245 
246  void*
247  VideoPicture :: getOpaqueData()
248  {
249  #ifdef __APPLE__
250  return mCMSampleBuffer;
251  #endif
252 
253  return NULL;
254  }
255 
256  void
258  {
259  if (!mBuffer || mBuffer->getBufferSize() < getSize())
260  allocInternalFrameBuffer();
261  unsigned char* buffer = (unsigned char*)mBuffer->getBytes(0, getSize());
262  // This is an inherently unsafe operation; it copies over all the bits in the AVFrame
263  memcpy(frame, mFrame, sizeof(AVFrame));
264  //*frame = *mFrame;
265  // and then relies on avpicture_fill to overwrite any areas in frame that
266  // are pointed to the wrong place.
267  av_image_fill_arrays(frame->data,
268  frame->linesize,
269  buffer,
270  (AVPixelFormat) frame->format,
271  frame->width,
272  frame->height,
273  1);
274  frame->quality = getQuality();
275 // frame->type = FF_BUFFER_TYPE_USER;
276  }
277 
278  void
280  int32_t width, int32_t height)
281  {
282  try
283  {
284  // Need to copy the contents of frame->data to our
285  // internal buffer.
286  VS_ASSERT(frame, "no frame?");
287  if (!(av_pix_fmt_desc_get((AVPixelFormat) pixel)->flags & AV_PIX_FMT_FLAG_HWACCEL)) {
288  VS_ASSERT(frame->data[0], "no data in frame");
289  // resize the frame to the AVFrame
290  mFrame->width = width;
291  mFrame->height = height;
292  mFrame->format = (int)pixel;
293 
294  int bufSize = getSize();
295  if (bufSize <= 0)
296  throw std::runtime_error("invalid size for frame");
297 
298  if (!mBuffer || mBuffer->getBufferSize() < bufSize)
299  // reuse buffers if we can.
300  allocInternalFrameBuffer();
301 
302  uint8_t* buffer = (uint8_t*)mBuffer->getBytes(0, bufSize);
303  if (!buffer)
304  throw std::runtime_error("really? no buffer");
305 
306  if (frame->data[0])
307  {
308  // Make sure the frame isn't already using our buffer
309  if(buffer != frame->data[0])
310  {
311  av_image_fill_arrays(mFrame->data, mFrame->linesize, buffer,
312  (AVPixelFormat) frame->format, width, height, 1);
313  av_image_copy(mFrame->data, mFrame->linesize, (const uint8_t **)frame->data,
314  frame->linesize, (AVPixelFormat)frame->format, frame->width, frame->height);
315  }
316  mFrame->key_frame = frame->key_frame;
317  }
318  else
319  {
320  throw std::runtime_error("no data in frame to copy");
321  }
322  }
323  else
324  {
325  mFrame->width = width;
326  mFrame->height = height;
327 // mFrame->format = (int) pixel;
328  if (pixel != IPixelFormat::MEDIACODEC)
329  {
330  //mFrame->hw_frames_ctx = frame->hw_frames_ctx;
331  av_hwframe_transfer_data(mFrame, frame, 0);
332  }
333  else
334  {
335  //av_frame_copy_props(mFrame, frame);
336  mFrame->data[3] = frame->data[3];
337  }
338  }
339  }
340  catch (std::exception & e)
341  {
342  VS_LOG_DEBUG("error: %s", e.what());
343  }
344  }
345 
346  AVFrame*
348  {
349  if (!mBuffer || mBuffer->getBufferSize() < getSize())
350  {
351  // reuse buffers if we can.
352  allocInternalFrameBuffer();
353  }
354  return mFrame;
355  }
356 
357  int
359  {
360  int retval = -1;
361  if (getAVFrame()
362  && lineNo >= 0
363  && (unsigned int) lineNo < (sizeof(mFrame->linesize)/sizeof(mFrame->linesize[0])))
364  retval = mFrame->linesize[lineNo];
365  return retval;
366  }
367 
368  bool
370  {
371  return (mFrame ? mFrame->key_frame : false);
372  }
373 
374  void
376  {
377  if (mFrame)
378  mFrame->key_frame = aIsKey;
379  }
380 
381  int64_t
383  {
384  return (mFrame ? mFrame->pts : -1);
385  }
386 
387  void
388  VideoPicture :: setPts(int64_t value)
389  {
390  if (mFrame)
391  mFrame->pts = value;
392  }
393 
394  int
396  {
397  return (mFrame ? mFrame->quality : FF_LAMBDA_MAX);
398  }
399 
400  void
402  {
403  if (newQuality < 0 || newQuality > FF_LAMBDA_MAX)
404  newQuality = FF_LAMBDA_MAX;
405  if (mFrame)
406  mFrame->quality = newQuality;
407  }
408 
409  void
411  bool aIsComplete,
412  IPixelFormat::Type format,
413  int width,
414  int height,
415  int64_t pts
416  )
417  {
418  try {
419  mIsComplete = aIsComplete;
420 
421  if (mIsComplete)
422  {
423  setPts(pts);
424  }
425 
426  if (!mFrame)
427  throw std::runtime_error("no AVFrame allocated");
428  if (format != IPixelFormat::NONE && mFrame->format != (int)IPixelFormat::NONE && (int)format != mFrame->format)
429  throw std::runtime_error("pixel formats don't match");
430  if (width > 0 && mFrame->width >0 && width != mFrame->width)
431  throw std::runtime_error("width does not match");
432  if (height > 0 && mFrame->height > 0 && height != mFrame->height)
433  throw std::runtime_error("height does not match");
434  }
435  catch (std::exception& e)
436  {
437  VS_LOG_DEBUG("error: %s", e.what());
438  }
439  }
440 
441  int32_t
443  {
444  int retval = -1;
445  if (mFrame->width > 0 && mFrame->height > 0)
446  retval = av_image_get_buffer_size((AVPixelFormat)mFrame->format, mFrame->width, mFrame->height, 1);
447  return retval;
448  }
449 
450  void
451  VideoPicture :: allocInternalFrameBuffer()
452  {
453  int bufSize = getSize();
454  if (bufSize <= 0)
455  throw std::runtime_error("invalid size for frame");
456 
457  // reuse buffers if we can.
458  if (!mBuffer || mBuffer->getBufferSize() < bufSize)
459  {
460  // Now, it turns out some accelerated assembly functions will
461  // read at least a word past the end of an image buffer, so
462  // we make space for that to happen.
463  // I arbitrarily choose the sizeof a long-long (64 bit).
464 
465  // note that if the user has passed in their own buffer that is
466  // the right 'size' but doesnt have this padding, we let it through anyway.
467  int extraBytes=sizeof(int64_t);
468 
469  // Make our copy buffer.
470  mBuffer = com::avpkit::ferry::IBuffer::make(this, bufSize+extraBytes);
471  if (!mBuffer) {
472  throw std::bad_alloc();
473  }
474 
475  // Now, to further work around issues, I added the extra 8-bytes,
476  // and now I'm going to zero just those 8-bytes out. I don't
477  // zero-out the whole buffer because I want Valgrind to detect
478  // if it's not written to first. But I know this overrun
479  // issue exists in the MMX conversions in SWScale for some libraries,
480  // so I'm going to fake it out here.
481  {
482  unsigned char * buf =
483  ((unsigned char*)mBuffer->getBytes(0, bufSize+extraBytes));
484 
485  memset(buf+bufSize, 0, extraBytes);
486  }
487  }
488  uint8_t* buffer = (uint8_t*)mBuffer->getBytes(0, bufSize);
489  if (!buffer)
490  throw std::bad_alloc();
491 
492  int imageSize = av_image_fill_arrays(mFrame->data,
493  mFrame->linesize,
494  buffer,
495  (AVPixelFormat)mFrame->format,
496  mFrame->width,
497  mFrame->height,
498  1);
499  if (imageSize != bufSize)
500  throw std::runtime_error("could not fill picture");
501 
502 // mFrame->type = FF_BUFFER_TYPE_USER;
503  VS_ASSERT(mFrame->data[0] != 0, "Empty buffer");
504  }
505 
508  {
509  IVideoPicture::PictType retval = IVideoPicture::DEFAULT_TYPE;
510  if (mFrame)
511  retval = (PictType) mFrame->pict_type;
512  return retval;
513  }
514 
515  void
517  {
518  if (mFrame)
519  mFrame->pict_type = (enum AVPictureType) type;
520  }
521 
522  void
523  VideoPicture::setSideData(IVideoPicture::FrameDataType type, com::avpkit::ferry::IBuffer* buffer) {
524  if (mFrame){
525  av_frame_new_side_data(mFrame, (AVFrameSideDataType)type, buffer->getSize());
526  }
527  }
528 
529 }}}
@ MEDIACODEC
HW decoding through Android MediaCodec
Definition: IPixelFormat.h:288
static IRational * make()
Get a new rational that will be set to 0/0.
Definition: IRational.cpp:79
Represents one raw (undecoded) picture in a video stream, plus a timestamp for when to display that v...
Definition: IVideoPicture.h:40
virtual int getWidth()=0
What is the width of the picture.
virtual int getHeight()=0
What is the height of the picture.
virtual IPixelFormat::Type getPixelType()=0
Returns the pixel format of the picture.
virtual bool isComplete()=0
Is this picture completely decoded?
PictType
The different types of images that we can set.
virtual int64_t getPts()=0
What is the Presentation Time Stamp (in Microseconds) of this picture.
virtual void setComplete(bool aIsComplete, IPixelFormat::Type format, int width, int height, int64_t pts)
After modifying the raw data in this buffer, call this function to let the object know it is now comp...
virtual void setData(com::avpkit::ferry::IBuffer *buffer)
Sets the underlying buffer used by this object.
virtual int64_t getPts()
What is the Presentation Time Stamp (in Microseconds) of this picture.
virtual int32_t getSize()
Total size in bytes of the decoded picture.
void copyAVFrame(AVFrame *frame, IPixelFormat::Type pixel, int32_t width, int32_t height)
Called by the StreamCoder once it's done decoding.
virtual void setKeyFrame(bool aIsKey)
Reset if this is a key frame or not.
virtual bool isKeyFrame()
Is this a key frame?
virtual void setPictureType(IVideoPicture::PictType type)
Set the picture type.
virtual com::avpkit::ferry::IBuffer * getData()
Get any underlying raw data available for this object.
virtual void setPts(int64_t)
Set the Presentation Time Stamp (in Microseconds) for this picture.
virtual IVideoPicture::PictType getPictureType()
Get the picture type.
virtual int getDataLineSize(int lineNo)
Return the size of each line in the VideoPicture data.
void fillAVFrame(AVFrame *frame)
Called by the StreamCoder before it encodes a picture.
virtual bool copy(IVideoPicture *srcFrame)
Copy the contents of the given picture into this picture.
virtual void setQuality(int newQuality)
Set the Quality to a new value.
virtual int getQuality()
This value is the quality setting this VideoPicture had when it was decoded, or is the value to use w...
static VideoPicture * make(IPixelFormat::Type format, int width, int height)
The default factory for a frame.
virtual void render(bool drop, int64_t timeStamp)
Render this picture on configured surface.
VS_API_AVPKIT AVFrame * getAVFrame()
Call to get the raw underlying AVFrame we manage; don't pass this to ffmpeg directly as ffmpeg often ...
Allows Java code to get data from a native buffers, and optionally modify native memory directly.
Definition: IBuffer.h:54
static IBuffer * make(RefCounted *requestor, void *bufToWrap, int32_t bufferSize, FreeFunc freeFunc, void *closure)
Allocate a new buffer by wrapping a native buffer.
Definition: IBuffer.cpp:48
virtual void * getBytes(int32_t offset, int32_t length)=0
Returns up to length bytes, starting at offset in the underlying buffer we're managing.
virtual int32_t getBufferSize()=0
Get the current maximum number of bytes that can be safely placed in this buffer.
virtual int32_t getSize()=0
Returns the size, in units of getType() of this buffer.
T * get()
Call RefCounted::acquire() on the managed pointer and return it.
Definition: RefPointer.h:206
void reset(T *ptr=0, bool acquire=false)
Reset the managed pointer, calling RefCounted::release() on the previously managed pointer first.
Definition: RefPointer.h:237
WARNING: Do not use logging in this class, and do not set any static file variables to values other t...